{
  "schema": "metrology-atlas/0.2",
  "dataset": {
    "id": "vitaldb",
    "name": "VitalDB metrology atlas",
    "summary": "Every recorded value in the VitalDB open dataset (6,388 surgical cases, PhysioNet release 1.0.0) as a probability distribution over when it happened and what it was, with the studies that measured each part.",
    "description": "A record in VitalDB is a pair: a recorded time and a recorded value. In the language of metrology, each record is\nan indication of two **measurands**: when the thing it describes happened, and what its value was. Neither is\nrecorded exactly. The time comes from one of a dozen clocks that drift and are corrected in jumps, and often\ndescribes a window that ended earlier; the value was rounded, sometimes heaped by hand, and measured by an\ninstrument with its own error.\n\nFor every variable, this atlas states the measurand, the **measurement model** that links it to the recorded\npair, and what is known about the measurand given a record, as a joint probability distribution over its true\ntime and true value (the multivariate case of the GUM, Supplement 2). Every part of the model carries its\nevidence: the study it came from and how it was evaluated (measured from the data, or estimated and assumed).\nWith the distributions known, uncertainty can be propagated through any calculation on the data (the GUM,\nSupplement 1). [Aim and terms](terms.md) states this aim in full and maps the atlas onto the international\nvocabulary.\n\nIt is a metrology of recorded data: the measurements were made long ago, so each part of the model is\nreconstructed from the records themselves.\n\n**All clock rates are relative.** Each is measured against another clock that also drifts, and the page says\nwhich. Every clock is linked, directly or through others, to the recorder PC ([[clock:recorder-pc]]); its link to\nUTC cannot be observed in the release and\nis assumed, so no time here is metrologically traceable to UTC: the chain is broken at its first link.\n",
    "overview": "VitalDB records each operating room with one PC running Vital Recorder. Every device sends its data to that PC,\nwhich stamps each record. The waveforms that matter most for timing come through SNUADC, a 500 Hz converter that\nsamples the patient monitor's analogue outputs (ECG, arterial pressure, plethysmogram, CVP); the BIS monitor,\nthe Primus anaesthesia machine and a handful of haemodynamic monitors send their own waveforms and numbers, each\non its own clock. The patient monitor's numbers (heart rate, pressures, SpO2) arrive as single values that\ndescribe a window of signal which ended seconds before their stamp.\n\nSo a time in VitalDB can be off in two ways: the clock that made it drifts and is corrected in jumps, and the value\nit is attached to describes an earlier interval. The chart below places every kind of time stamp on both axes.\n",
    "reference_clock": "snuadc-sample",
    "reference": "The working reference is **SNUADC stamp-model time**: the SNUADC sample clock, placed on the recorder PC's\ntimeline by the stamp model (drift and corrections fitted, the signal lost at each correction added back). Every\ndelay in the planner is measured against it. Its own error against the PC is a few milliseconds where the\nSNUADC's corrections are seen; in recordings that make no SNUADC correction it rests on the port's typical rate\nand can drift by about ±0.1 s per hour ([[observation:snuadc-long-uncorrected]]). The PC's error against UTC is\nassumed.\n",
    "figures": [
      {
        "id": "system-overview",
        "file": "timing-graph.svg",
        "interactive": true,
        "caption": "The timing graph: every clock in the atlas, placed one row below the clock it is measured against, with each\nlink's drift and correction interval. Generated from the atlas's clock, link and parameter pages on every\nbuild, so it shows what the atlas currently holds; every box and label links to its page. All device rates\nare relative to the recorder PC, whose own link to UTC is assumed.\n",
        "alt": "A tree with UTC at the top, the recorder PC clock below it, a row of device clocks (Primus, CardioQ, SNUADC, BIS, hospital records) below that, and the clocks measured against the SNUADC (pulse oximeter frames, monitor output, pacemaker, Solar8000) at the bottom; each line labelled with a drift and a correction interval."
      },
      {
        "id": "synchronisation-demo",
        "file": "sync-demo-1216.png",
        "theme": "example traces",
        "source": "vitaldb-bench tools/sync_demo.py",
        "records": [
          1216
        ],
        "caption": "One recording ([[case:1216]], 8.7 hours), before synchronisation (left) and after (right), on the same scales. Top: each clock's time minus the synchronised time. The SNUADC's stamps drift and are pulled back by half a second about every two hours; the BIS's stamps sit about 0.8 s off and are pushed forward every 8,192 s; the Primus drifts about 4 s over the recording and steps at its airway-pressure pauses (dotted lines). After synchronisation the typical error left is about 3 ms (SNUADC), 42 ms (BIS) and 55 ms (Primus, whole-release medians; bands). Middle: the same electrocautery and muscle bursts seen in the ECG and the EEG, as recorded and on the synchronised times, a check independent of the correction. Bottom: the airway-to-central-venous pressure lag of the breath cycle, as recorded and, after, as the error left when each 10-minute window is predicted from all the others.",
        "alt": "Six panels over 9 hours. Left column: sawtooth lines for SNUADC and BIS and a rising line for the Primus spanning about 3.5 seconds; burst lags ramping between minus 0.6 and minus 1.2 seconds; breath lags rising from minus 2 to plus 2 seconds. Right column: the same quantities flat near zero."
      },
      {
        "id": "synchronisation-census",
        "file": "sync-error-census.png",
        "theme": "population results",
        "source": "vitaldb-bench tools/sync_error_census.py",
        "caption": "Timing error per window across the whole release, as recorded (grey) and after synchronisation (blue), on a log scale; dashed lines mark the medians. Each link is checked by a signal both clocks record, which never enters the correction it tests. BIS against SNUADC, by electrocautery and muscle bursts in the ECG and the EEG: median 794 ms to 42 ms (2,737 recordings, 10-minute windows). Primus against SNUADC, by the breath cycle in airway pressure and CVP: 378 ms to 55 ms (1,067 recordings; each window predicted from all the others, a line per stretch between AWP pauses). Primus against the PC, by PC-stamped PIP values: 450 ms to 238 ms (172 recordings, 30-minute blocks). The \"before\" for both Primus links has each recording's own offset removed, so it understates the raw error.",
        "alt": "Three histograms on a log time axis. In each the grey \"as recorded\" distribution sits to the right, at hundreds of milliseconds to seconds, and the blue \"synchronised\" distribution sits to the left, at tens of milliseconds for BIS and the Primus against SNUADC, and a few hundred milliseconds for the Primus against the PC."
      }
    ],
    "utc_step": {
      "node": "Recorder PC clock",
      "clock": "recorder-pc",
      "kind": "sync",
      "effect": "Windows time synchronisation to the hospital's time server every 8–24 h (assumed), drifting a few ppm in between. Not observable in the release: dates are shifted to 2100. No step of 0.5 s or more was found.",
      "offset_s": [
        0.003,
        0.9
      ],
      "status": "assumed",
      "link": "recorder-pc-vs-utc",
      "parameter": "pc-utc-offset"
    },
    "domains": [
      {
        "id": "reference",
        "name": "SNUADC stamp-model time (the working reference)",
        "clock": "snuadc-sample",
        "drift_ppm": 0,
        "drift_status": "measured",
        "note": "Signals synchronised to the reference share it, so drift cancels between them."
      },
      {
        "id": "snuadc-raw",
        "name": "SNUADC raw record stamps",
        "clock": "snuadc-sample",
        "note": "A sawtooth of drift and half-second corrections, not a steady drift."
      },
      {
        "id": "bis",
        "name": "BIS clock",
        "clock": "bis-clock",
        "drift_ppm": 25,
        "drift_status": "measured",
        "note": "Against the recorder PC; corrected on a fixed 8,192 s timer."
      },
      {
        "id": "primus",
        "name": "Primus clock",
        "clock": "primus-clock",
        "drift_ppm": 104,
        "drift_status": "measured",
        "note": "Against the recorder PC, by breath matching through the SNUADC stamp model (p10–p90 +28 to +184 ppm); steps of up to about ±0.5 s at the AWP pauses."
      },
      {
        "id": "solar8000",
        "name": "Solar8000 numerics",
        "clock": "solar8000-clock"
      },
      {
        "id": "cardioq",
        "name": "CardioQ clock",
        "clock": "cardioq-clock"
      },
      {
        "id": "ev1000",
        "name": "EV1000"
      },
      {
        "id": "vigileo",
        "name": "Vigileo"
      },
      {
        "id": "vigilance",
        "name": "Vigilance"
      },
      {
        "id": "invos-fms",
        "name": "Invos and FMS"
      },
      {
        "id": "pumps",
        "name": "Infusion pumps"
      },
      {
        "id": "records",
        "name": "Clinical and lab records",
        "clock": "hospital-records"
      }
    ],
    "references": [
      "lee2022vitaldb",
      "lee2022vitaldb-physionet",
      "pollard2026physionet"
    ],
    "reference_groups": [
      "Metrology",
      "Data and recording",
      "Timing and synchronisation",
      "Coarse data and heaping",
      "Anthropometry",
      "Pulse arrival time",
      "The authors' earlier work",
      "Software"
    ],
    "figure_themes": [
      "drift and resets",
      "data continuity",
      "correction intervals",
      "delay and window fits",
      "distributions of measured parameters",
      "agreement and residuals",
      "heaping",
      "distributions of recorded values",
      "example traces",
      "schematics"
    ],
    "variable_groups": [
      "Waveforms",
      "Numerics",
      "Events",
      "Times and durations",
      "Patient",
      "Operation and anaesthesia",
      "Preoperative history",
      "Airway and lines",
      "Fluids and blood",
      "Drugs",
      "Outcome",
      "Preoperative labs",
      "Blood gas",
      "Electrolytes and glucose",
      "Haematology",
      "Coagulation",
      "Chemistry",
      "Result times"
    ],
    "pages": [
      {
        "id": "setups",
        "title": "Recording setups",
        "file": "setups.md",
        "section": "Time"
      },
      {
        "id": "how-it-was-made",
        "title": "How this atlas was made",
        "file": "how-it-was-made.md"
      },
      {
        "id": "contributing",
        "title": "Contributing",
        "file": "contributing.md"
      },
      {
        "id": "data-and-licence",
        "title": "Data and licence",
        "file": "data-and-licence.md"
      }
    ],
    "sources": [
      {
        "software": "vitaldb-resolution",
        "folder": "_sources/vitaldb-resolution",
        "commit": "bb03ddfc8acad2fed25fe51c378b3b0b6e7ad5ea",
        "date": "2026-10-18",
        "note": "Registry and fitted value models. Refreshed by _import/resolution.py; never edited here."
      }
    ],
    "site": {
      "title": "VitalDB metrology atlas",
      "url": "https://vitaldb.metrology-atlas.org/",
      "repo_url": "https://github.com/metrology-atlas/metrology-atlas",
      "copyright": "© 2026 Andrew J. Goodwin, University of Sydney · Text and figures CC BY 4.0 · Code MIT · Data: VitalDB (CC BY 4.0); see Data and licence"
    },
    "records": {
      "name": "case",
      "label": "case {id}",
      "max": 6388,
      "policy": "VitalDB is an open dataset (CC BY 4.0), and its case ids are public. The atlas names a case only where a study\ndiscusses a specific example: an edge case, a recording that shows a mechanism clearly, or a comparison between\ntwo cases. Population and group results, and figures that show a general principle, carry no case ids. Every\nnamed case is listed here with the pages that discuss it, so each example can be opened and checked.\n"
    },
    "forbidden_patterns": [
      {
        "pattern": "(?i)\\bcase\\s*(?:id\\s*)?#?\\s*\\d{1,4}\\b",
        "why": "name a case only deliberately, as [[case:1234]], so it is listed on the cases page"
      },
      {
        "pattern": "\\bvitaldb-\\d{4}\\b",
        "why": "name a case only deliberately, as [[case:1234]]"
      },
      {
        "pattern": "\\b\\d{4}\\.vital\\b",
        "why": "name a case only deliberately, as [[case:1234]]"
      }
    ],
    "notice": "**Pre-release, open for review (v0.1.0, October 2026).** This first public version accompanies a preprint and is\nreleased for review by the community and the dataset's custodians ahead of a peer-reviewed journal version.\nComments are welcome as [issues or discussions](https://github.com/metrology-atlas/metrology-atlas) on GitHub.\n\nStudies are still being added and revised, some numbers rest on small runs, and\nmany questions are open. Every page says how its numbers are known. If you find something wrong, or can settle an\nopen question, please [contribute](contributing.md): corrections, better models and new studies are all welcome.\n\nThe studies, code and pages were developed with substantial assistance from an AI assistant (Claude, by\nAnthropic), under the author's direction and review. Mistakes may remain; [how this atlas was\nmade](how-it-was-made.md) says more.\n",
    "anchor_default": {
      "basis": "convention",
      "note": "Assumed to round to the nearest step, the usual rule for analyser results and charted numbers. Not tested; the largest possible effect is half a step, small for most of these against the measurement's own error."
    },
    "examples_intro": "Each worked example takes an analysis people commonly run on VitalDB and does it twice: once on the stored\nnumbers, and once with the atlas's corrections, timing on the time side, the rounding models on the value side.\nEach page states the question, why the atlas changes the answer, the steps, and the code to rerun it, so the same\ncorrections can be carried into your own work. The time-side examples show what misaligned clocks do to derived\nsignals; the value-side examples show results as distributions instead of single numbers.\n"
  },
  "clocks": {
    "bis-clock": {
      "id": "bis-clock",
      "name": "BIS clock",
      "summary": "The BIS monitor's own clock.",
      "type": "oscillator",
      "nominal": "128 Hz EEG sample clock",
      "description": "The BIS monitor's own clock. Its EEG stamps drift by about +25 ppm against the recorder PC and are corrected on a fixed 8,192 s timer.",
      "tags": [
        "stub"
      ],
      "utc_plan": "Two routes, and they close: through its own stamps to the PC, and through shared bursts to SNUADC. A constant latency (−0.80 s or −0.66 s by SNUADC mode, whole release) belongs in the link. To strengthen: use the cardiac artefact where there are no bursts.",
      "derived": {
        "spaces": [
          "bis-eeg"
        ],
        "stamps": [
          "bis-index"
        ],
        "devices": [
          "bis"
        ],
        "links": [
          "bis-vs-pc",
          "bis-vs-snuadc"
        ],
        "studies": [
          "bis-cardiac-artefact",
          "clock-correction-scan",
          "continuity-gaps-segments",
          "correction-periods",
          "device-correction-schedules",
          "drift-units",
          "loop-closure",
          "record-messages-and-steps",
          "recorder-pc-steps",
          "shared-bursts",
          "snuadc-stamp-sawtooth",
          "step-classification"
        ],
        "observations": [
          "device-drift-by-setup",
          "eeg-ecg-offset-by-mode"
        ]
      }
    },
    "cardioq-clock": {
      "id": "cardioq-clock",
      "name": "CardioQ clock",
      "summary": "The CardioQ's own clock; the recorder re-anchors its stamps about every 5 minutes.",
      "type": "oscillator",
      "nominal": "Waveform sample clock",
      "description": "The CardioQ's own clock; the recorder re-anchors its stamps about every 5 minutes.",
      "tags": [
        "stub"
      ],
      "utc_plan": "Re-anchored about every 5 minutes against the PC, and matched beat by beat to SNUADC per stretch; the three-way arterial pressure loop with the EV1000 and SNUADC closes. Enough for second-level alignment; no drift within a stretch is modelled.",
      "derived": {
        "spaces": [
          "cardioq-abp"
        ],
        "devices": [
          "cardioq"
        ],
        "links": [
          "cardioq-vs-pc",
          "cardioq-vs-snuadc"
        ],
        "studies": [
          "clock-correction-scan",
          "loop-closure",
          "rare-device-sync"
        ]
      }
    },
    "hospital-records": {
      "id": "hospital-records",
      "name": "Hospital records clock",
      "summary": "The clocks of the anaesthesia record and the laboratory system, and the rounding of what was entered.",
      "type": "software",
      "nominal": "Clinical and laboratory systems",
      "description": "The clocks of the anaesthesia record and the laboratory system, and the rounding of what was entered. Not observable in the release except through events seen in the signals. What its stamps mark is known better than its offset: anaesthesia start comes a median 14 minutes before device-marked induction ([[example:induction-hypotension]]), entries on 5-minute marks are rounded up, clearly so at anaesthesia end ([[study:emr-rounding]]; the value models disagree for the starts and for operation end), and a blood gas's lab time comes a median 7.2 minutes after the draw ([[parameter:lab-bg-lag]]).",
      "tags": [
        "stub"
      ],
      "utc_plan": "Not observable: the clinical and lab times share the case start with the recorder, so any offset cancels, and it is confounded with documentation lag. Signal-side events (drug effects, arterial draws) bound it to minutes. To measure it needs a time from outside the release.",
      "derived": {
        "stamps": [
          "alb",
          "alt",
          "ammo",
          "aneend",
          "anestart",
          "aptt",
          "ast",
          "be",
          "bun",
          "case-end",
          "ccr",
          "cl",
          "cr",
          "crp",
          "esr",
          "fib",
          "gfr",
          "gluc",
          "hb",
          "hco3",
          "hct",
          "ica",
          "k",
          "lab-dt",
          "lac",
          "na",
          "opend",
          "opstart",
          "p",
          "pco2",
          "ph",
          "plt",
          "po2",
          "pt-pct",
          "ptinr",
          "ptsec",
          "sao2",
          "tbil",
          "tprot",
          "wbc",
          "admission-discharge",
          "emr-events",
          "lab-blood-gas",
          "lab-central"
        ],
        "devices": [
          "clinical-records"
        ],
        "links": [
          "hospital-records-vs-pc"
        ],
        "studies": [
          "case-clock",
          "emr-rounding",
          "lab-timestamp-meaning",
          "loop-closure"
        ],
        "observations": [
          "emr-offset-confounded",
          "emr-rounded-up",
          "icu-days-exceed-postop-stay",
          "imputed-op-ends",
          "lab-time-is-specimen"
        ]
      }
    },
    "monitor-output-clock": {
      "id": "monitor-output-clock",
      "name": "Monitor analogue output clock",
      "summary": "A line at 240 Hz in the arterial pressure and plethysmogram (weakly in the ECG) runs −25 to −29 ppm against the SNUADC sample clock: most likely the patient monitor's output stage.",
      "type": "oscillator",
      "nominal": "240 Hz line in the analogue outputs",
      "tags": [
        "stub"
      ],
      "utc_plan": "Measured against SNUADC on 6 files only. To strengthen: run the line scan on all cases, settle whether the line is 240 Hz or an aliased 260 Hz (which flips the sign), and match it to the PC-stamped Solar8000 numerics for a second route.",
      "derived": {
        "links": [
          "monitor-output-vs-snuadc"
        ],
        "parameters": [
          "monitor-240hz-rate"
        ],
        "studies": [
          "monitor-lines",
          "snuadc-firmware"
        ]
      }
    },
    "oximeter-frame-clock": {
      "id": "oximeter-frame-clock",
      "name": "Pulse oximeter frame clock",
      "summary": "The oximeter module plays the plethysmogram out in 33.3 ms (30 Hz) frames on its own clock, fixed per recording setup at +7 to +92 ppm against the SNUADC sample clock (medians over 42 PC keys).",
      "type": "oscillator",
      "nominal": "30 Hz output frames",
      "description": "Two rates live inside the oximeter. The frame clock sets when the output changes; the PPG content runs at a\nslightly different rate against those frames, so a frame of lag builds up and one frame is repeated: the\nsawtooth ([[link:pleth-content-vs-frames]]). Raw-sample PPG fiducials lock to the frame grid and reveal it.\n",
      "utc_plan": "Measured against SNUADC two ways (fiducial frame locking, and the 30 Hz lines), then through SNUADC to the PC. The PPG content's own drift against the frames (the sawtooth) is corrected tooth by tooth.",
      "derived": {
        "links": [
          "oximeter-frames-vs-snuadc",
          "pleth-content-vs-frames"
        ],
        "studies": [
          "monitor-lines",
          "pat-offset-census",
          "ppg-art-reference",
          "ppg-frame-clock",
          "ppg-offset-reference",
          "ppg-sawtooth-pauses",
          "ppg-sawtooth"
        ],
        "observations": [
          "oximeter-frame-clock-fingerprint",
          "ppg-sawtooth-pauses",
          "ppg-sawtooth"
        ]
      }
    },
    "pacemaker": {
      "id": "pacemaker",
      "name": "Pacemaker clock",
      "summary": "The crystal of an implanted pacemaker. At a fixed pacing rate it beats at one constant interval that no device in the recording chain can touch, which makes it an independent reference.",
      "type": "oscillator",
      "nominal": "Fixed pacing interval (about 1,204, 1,000 or 860 ms in VitalDB's paced recordings)",
      "description": "Only fixed-rate (asynchronous) pacing works as a clock. A demand pacemaker resets its interval at every\nintrinsic or fusion beat, so its phase cannot be carried across such a beat.\n",
      "utc_plan": "Not needed for synchronisation: the pacemaker is a reference, used to test the SNUADC timeline. Its own absolute rate is unknown.",
      "derived": {
        "devices": [
          "pacemaker"
        ],
        "links": [
          "pacemaker-vs-snuadc"
        ],
        "parameters": [
          "pacemaker-interval-agreement"
        ],
        "studies": [
          "paced-ecg"
        ]
      }
    },
    "primus-clock": {
      "id": "primus-clock",
      "name": "Primus clock",
      "summary": "The Primus anaesthesia machine's own clock.",
      "type": "oscillator",
      "nominal": "62.5 Hz waveform clock",
      "description": "The Primus anaesthesia machine's own clock. Its stamps are re-anchored at recording gaps and by small corrections about every two hours. Measured by breath matching, it drifts a median +104 ppm against the recorder PC, which its small corrections do not keep up with.",
      "tags": [
        "stub"
      ],
      "utc_plan": "Measured to the PC by breath matching through the SNUADC stamp model: +104 ppm within the stretches between AWP pauses (p10–p90 +28 to +184, 178 recordings), with steps at the pauses that do not reset the drift: tens of ms where CO2 carries on, up to seconds where CO2 stops too ([[study:primus-pause-steps]]). The small correction steps imply only 1–11 ppm and are not the clock rate. Open: what re-anchors the stamps where CO2 stops too; the airway-to-CVP physiological delay inside the measured lag.",
      "derived": {
        "spaces": [
          "primus-awp",
          "primus-co2"
        ],
        "devices": [
          "primus"
        ],
        "links": [
          "primus-vs-pc"
        ],
        "studies": [
          "airway-heartbeat-pilot",
          "clock-correction-scan",
          "continuity-gaps-segments",
          "correction-periods",
          "device-correction-schedules",
          "loop-closure",
          "primus-pause-steps",
          "record-messages-and-steps",
          "step-classification",
          "ventilator-breath-matching"
        ],
        "observations": [
          "co2-cardiac-trace",
          "primus-awp-pauses"
        ]
      }
    },
    "recorder-pc": {
      "id": "recorder-pc",
      "name": "Recorder PC clock",
      "summary": "The Windows clock of the PC running Vital Recorder. It stamps every record, and every other clock is linked to it, directly or through others.",
      "type": "software",
      "nominal": "Windows system time, kept by network time synchronisation",
      "description": "Vital Recorder stamps each record on arrival with this clock, and every device link is measured against it. Its\nown link to UTC cannot be observed in the release, because dates are shifted to the year 2100, so that link is an\nexplicit assumption: a few ppm of drift, corrected by Windows time synchronisation every 8–24 h.\n\nIt does not step visibly: across all 6,388 files no simultaneous jump of every device of 0.5 s or more was found,\nand it never steps backwards.\n\nEvery device drift in the atlas is measured against this clock, so its own drift and corrections are inside\nevery one of them. Within a recording the BIS and SNUADC drifts vary from interval to interval, but in opposite\ndirections (r = −0.54), which a PC excursion would not produce ([[study:device-correction-schedules]]).\n\nThe recording itself pauses for about 18 s on a 5-hour timer that does not start with the recording\n([[observation:recording-pauses]]); whether that timer belongs to the PC or to Vital Recorder is not known.\n",
      "references": [
        "lee2022vitaldb",
        "rfc5905"
      ],
      "utc_plan": "The only link the release cannot show. What is known: no step of 0.5 s or more in any file. To go further needs evidence from outside the release: the recorder PCs' time-synchronisation logs or settings from the hospital, or recordings with a known external time (a dated event, a GPS-stamped device).",
      "derived": {
        "stamps": [
          "casestart",
          "bis-eeg",
          "cardioq-abp",
          "ev1000-art-mbp",
          "fms-infusion",
          "invos-rso2",
          "primus-awp",
          "primus-co2",
          "primus-numerics",
          "pump-rates",
          "snuadc-art",
          "snuadc-ecg-ii",
          "snuadc-pleth",
          "solar8000-art-numerics",
          "solar8000-hr",
          "solar8000-nibp",
          "solar8000-pleth-hr",
          "solar8000-spo2",
          "vigilance-co",
          "vigileo-sv"
        ],
        "devices": [
          "vital-recorder"
        ],
        "links": [
          "bis-vs-pc",
          "cardioq-vs-pc",
          "hospital-records-vs-pc",
          "primus-vs-pc",
          "recorder-pc-vs-utc",
          "snuadc-frequent-vs-pc",
          "snuadc-step-vs-pc"
        ],
        "parameters": [
          "pc-step-bound",
          "recording-pause"
        ],
        "studies": [
          "case-clock",
          "clock-correction-scan",
          "continuity-gaps-segments",
          "correction-periods",
          "device-correction-schedules",
          "drift-units",
          "emr-rounding",
          "loop-closure",
          "numeric-clock-check",
          "paced-ecg",
          "primus-pause-steps",
          "rare-device-sync",
          "record-messages-and-steps",
          "recorder-pc-steps",
          "reset-cluster-order",
          "snuadc-stamp-sawtooth",
          "solar-snuadc-clock",
          "stamp-arithmetic",
          "step-classification",
          "time-zeros",
          "vitaldb-loader"
        ],
        "observations": [
          "device-drift-by-setup",
          "emr-offset-confounded",
          "recording-pauses",
          "release-time-shift",
          "snuadc-no-timestamps"
        ]
      }
    },
    "snuadc-sample": {
      "id": "snuadc-sample",
      "name": "SNUADC sample clock",
      "summary": "The sample clock of the SNUADC acquisition board, which digitises ECG, arterial pressure, plethysmogram and CVP at 500 Hz. The working reference for every waveform alignment.",
      "type": "oscillator",
      "nominal": "500 Hz ADC sample clock",
      "modes": [
        {
          "id": "step",
          "name": "Step mode",
          "description": "The recorder lets the stamps drift, then pulls the track back by about half a second once the drift reaches that size. See [[link:snuadc-step-vs-pc]]."
        },
        {
          "id": "frequent",
          "name": "Frequent-correction mode",
          "description": "Stamps follow the PC closely, with occasional forward steps of 5–15 ms (about one an hour). Between corrections the stamps show only stamp arithmetic; the corrections themselves suggest a drift against the PC of a few ppm, which is not yet reconciled ([[study:stamp-arithmetic]])."
        }
      ],
      "description": "All SNUADC channels share this clock: they start together and carry the same record timing. The public firmware\nof the board (an ATmega32U4 with a 16 MHz crystal) schedules each sample 2 ms after the last, or at once if it\nis late, so a stall of more than 2 ms re-anchors the sample grid and the stalled time is never sampled. Whether\nthat firmware is the version deployed for VitalDB is not known.\n",
      "utc_plan": "Measured to the recorder PC in both modes (stamp sawtooth, confirmed by the paced ECG and the PC-stamped heart rate); from there the route rests on the assumed PC–UTC link. The reset-order fix and the stamp-arithmetic census are done (17 October). To strengthen: explain the long recordings with no correction, and give each recording without a correction its own rate rather than its port's.",
      "derived": {
        "spaces": [
          "snuadc-art",
          "snuadc-ecg-ii",
          "snuadc-pleth"
        ],
        "devices": [
          "snuadc"
        ],
        "links": [
          "bis-vs-snuadc",
          "cardioq-vs-snuadc",
          "monitor-output-vs-snuadc",
          "oximeter-frames-vs-snuadc",
          "pacemaker-vs-snuadc",
          "pleth-content-vs-frames",
          "snuadc-frequent-vs-pc",
          "snuadc-step-vs-pc",
          "solar8000-vs-snuadc"
        ],
        "parameters": [
          "pacemaker-interval-agreement",
          "snuadc-stamp-arithmetic"
        ],
        "studies": [
          "airway-heartbeat-pilot",
          "bis-cardiac-artefact",
          "clock-correction-scan",
          "continuity-gaps-segments",
          "correction-periods",
          "device-correction-schedules",
          "drift-units",
          "loop-closure",
          "monitor-lines",
          "numeric-clock-check",
          "paced-ecg",
          "ppg-art-reference",
          "ppg-frame-clock",
          "ppg-sawtooth",
          "primus-pause-steps",
          "rare-device-sync",
          "record-messages-and-steps",
          "recorder-pc-steps",
          "reset-cluster-order",
          "shared-bursts",
          "snuadc-firmware",
          "snuadc-stamp-sawtooth",
          "solar-snuadc-clock",
          "stamp-arithmetic",
          "step-classification",
          "ventilator-breath-matching",
          "vitaldb-loader"
        ],
        "observations": [
          "co2-cardiac-trace",
          "device-drift-by-setup",
          "eeg-ecg-offset-by-mode",
          "oximeter-frame-clock-fingerprint",
          "pleth-30hz-frames",
          "reset-records-out-of-order",
          "reset-signal-lost",
          "snuadc-mode-setups",
          "snuadc-no-timestamps",
          "snuadc-one-clock"
        ]
      }
    },
    "solar8000-clock": {
      "id": "solar8000-clock",
      "name": "Solar8000 clock",
      "summary": "The Solar8000 monitor's internal timing of its numeric windows.",
      "type": "unknown",
      "nominal": "Monitor numerics windows",
      "description": "The Solar8000 monitor's internal timing of its numeric windows. The values are stamped by the recorder on arrival, so only their delay is seen.",
      "tags": [
        "stub"
      ],
      "utc_plan": "No device clock is visible: the numbers are stamped by the PC on arrival. Their per-case delays behind SNUADC are fixed within a case, which bounds the monitor's drift. To strengthen: the monitor output clock (240 Hz line) measures the monitor against SNUADC directly.",
      "derived": {
        "devices": [
          "solar8000"
        ],
        "links": [
          "solar8000-vs-snuadc"
        ],
        "studies": [
          "solar-snuadc-clock"
        ]
      }
    },
    "utc": {
      "id": "utc",
      "name": "UTC",
      "summary": "Coordinated Universal Time, the reference every chain of clocks in the atlas starts from.",
      "type": "reference",
      "nominal": "Civil time",
      "description": "No clock in the release is tied to UTC directly. The recorder PC is synchronised to a hospital time server, and\nthe release shifts every date to the year 2100, so the offset of any VitalDB time from UTC is an assumption,\nbounded by the PC's synchronisation ([[link:recorder-pc-vs-utc]]).\n",
      "derived": {
        "links": [
          "recorder-pc-vs-utc"
        ],
        "studies": [
          "recorder-pc-steps"
        ],
        "observations": [
          "release-time-shift"
        ]
      }
    }
  },
  "devices": {
    "bis": {
      "id": "bis",
      "tracks": [
        {
          "track": "EEG1_WAV",
          "kind": "waveform",
          "unit": "uV",
          "format": "float32",
          "rate": "128 Hz",
          "gain": 1.0,
          "offset": 0.0,
          "recordings": 5566,
          "step": "0.05",
          "span": "codes -1476.7–1800.05 (65,536 used); 400 corrupt samples outside",
          "bits_to_store": 16,
          "converter": "16 bits",
          "evidence": "all 65,536 steps of 0.05 µV between −1476.70 and +1800.05 used, none outside"
        },
        {
          "track": "EEG2_WAV",
          "kind": "waveform",
          "unit": "uV",
          "format": "float32",
          "rate": "128 Hz",
          "gain": 1.0,
          "offset": 0.0,
          "recordings": 5566,
          "step": "0.05",
          "span": "codes -1476.7–1800.05 (65,536 used); 400 corrupt samples outside",
          "bits_to_store": 16,
          "converter": "16 bits",
          "evidence": "all 65,536 steps of 0.05 µV between −1476.70 and +1800.05 used, none outside"
        },
        {
          "track": "BIS",
          "kind": "numeric",
          "format": "float32",
          "rate": "1 s",
          "recordings": 5566,
          "grid": 0.1,
          "coarser_share": 0.267,
          "range": "0 to 97.7",
          "note": "corrupt values to -1.33e+03"
        },
        {
          "track": "EMG",
          "kind": "numeric",
          "unit": "dB",
          "format": "float32",
          "rate": "1 s",
          "recordings": 5566,
          "grid": 0.01,
          "coarser_share": 0.2,
          "range": "18.67 to 81.21"
        },
        {
          "track": "SEF",
          "kind": "numeric",
          "unit": "Hz",
          "format": "float32",
          "rate": "1 s",
          "recordings": 5566,
          "grid": 0.01,
          "coarser_share": 0.169,
          "range": "2.63 to 29.4"
        },
        {
          "track": "SQI",
          "kind": "numeric",
          "unit": "%",
          "format": "float32",
          "rate": "1 s",
          "recordings": 5566,
          "grid": 0.1,
          "coarser_share": 0.392,
          "range": "0 to 100",
          "note": "corrupt values to -2.15e+08 and 1.34e+07"
        },
        {
          "track": "SR",
          "kind": "numeric",
          "unit": "%",
          "format": "float32",
          "rate": "1 s",
          "recordings": 5566,
          "grid": 0.1,
          "coarser_share": 0.885,
          "range": "0 to 100",
          "note": "corrupt values to -1.66e+03 and 2.12e+03"
        },
        {
          "track": "TOTPOW",
          "kind": "numeric",
          "unit": "dB",
          "format": "float32",
          "rate": "1 s",
          "recordings": 5566,
          "grid": 0.01,
          "coarser_share": 0.2,
          "range": "24.98 to 82.5"
        }
      ],
      "name": "BIS monitor",
      "summary": "Medtronic BIS: raw EEG at 128 Hz and the BIS index.",
      "type": "monitor",
      "clocks": [
        "bis-clock"
      ],
      "tags": [
        "stub"
      ],
      "docs": [
        {
          "title": "VitalDB connection guide: Medtronic BIS VISTA",
          "url": "https://vitaldb.net/docs/?documentId=VitalRecorder%2Fconnection-guide%2Fdevices%2Fbrain_monitors%2Fmedtronic_bis_vista.md"
        },
        {
          "title": "VitalDB connection guide: Medtronic BIS A-2000",
          "url": "https://vitaldb.net/docs/?documentId=VitalRecorder%2Fconnection-guide%2Fdevices%2Fbrain_monitors%2Fmedtronic_bis_a2000.md"
        }
      ],
      "derived": {
        "variables": [
          "bis-eeg",
          "bis-index"
        ],
        "studies": [
          "bis-cardiac-artefact",
          "bis-index-delay",
          "clock-correction-scan",
          "continuity-gaps-segments",
          "correction-periods",
          "device-correction-schedules",
          "drift-units",
          "drug-effect-latency",
          "emr-rounding",
          "loop-closure",
          "numeric-derivation-pilot",
          "openeeg-bis-mimic",
          "record-messages-and-steps",
          "recorder-pc-steps",
          "setup-identification",
          "shared-bursts",
          "snuadc-stamp-sawtooth",
          "step-classification",
          "track-value-resolution"
        ],
        "observations": [
          "bis-eeg-cardiac-artefact",
          "bis-eeg-no-heartbeat",
          "bis-index-slow",
          "device-drift-by-setup",
          "duplicated-records",
          "eeg-ecg-offset-by-mode",
          "interleaved-records",
          "numerics-not-front-stamped"
        ]
      }
    },
    "cardioq": {
      "id": "cardioq",
      "tracks": [
        {
          "track": "ABP",
          "kind": "waveform",
          "unit": "mmHg",
          "format": "float32",
          "rate": "180 Hz",
          "gain": 1.0,
          "offset": 0.0,
          "recordings": 29,
          "step": "1",
          "span": "codes -100–414 (497 used); 2 corrupt samples outside",
          "bits_to_store": 10,
          "converter": "unknown, at least 10 bits",
          "evidence": "whole mmHg over 515 values: a display resolution"
        },
        {
          "track": "FLOW",
          "kind": "waveform",
          "unit": "cm/sec",
          "format": "float32",
          "rate": "180 Hz",
          "gain": 1.0,
          "offset": 0.0,
          "recordings": 29,
          "step": "1 (0–255), 2 (256–510), 4 (512–1016)",
          "span": "values 0–1016 (509 used): codes of a 9-bit segmented scale",
          "bits_to_store": 9,
          "converter": "unknown",
          "evidence": "a 9-bit segmented code, steps 1, 2 and 4 ([[observation:cardioq-flow-segmented]])"
        },
        {
          "track": "CO",
          "kind": "numeric",
          "unit": "L/min",
          "format": "float32",
          "rate": "3.68 s",
          "recordings": 29,
          "grid": 0.1,
          "coarser_share": 0.353,
          "range": "0 to 114"
        },
        {
          "track": "FTc",
          "kind": "numeric",
          "format": "float32",
          "rate": "3.68 s",
          "recordings": 29,
          "grid": 1.0,
          "coarser_share": 0.353,
          "range": "0 to 1496"
        },
        {
          "track": "HR",
          "kind": "numeric",
          "unit": "/min",
          "format": "float32",
          "rate": "3.68 s",
          "recordings": 29,
          "grid": 1.0,
          "coarser_share": 0.349,
          "range": "0 to 540"
        },
        {
          "track": "MD",
          "kind": "numeric",
          "format": "float32",
          "rate": "3.68 s",
          "recordings": 29,
          "grid": 1.0,
          "coarser_share": 0.353,
          "range": "0 to 5013"
        },
        {
          "track": "SD",
          "kind": "numeric",
          "format": "float32",
          "rate": "3.68 s",
          "recordings": 29,
          "grid": 0.1,
          "coarser_share": 0.347,
          "range": "0 to 933"
        },
        {
          "track": "SV",
          "kind": "numeric",
          "unit": "mL",
          "format": "float32",
          "rate": "3.68 s",
          "recordings": 29,
          "grid": 1.0,
          "coarser_share": 0.349,
          "range": "0 to 7297"
        },
        {
          "track": "CI",
          "kind": "numeric",
          "unit": "L/min/m2",
          "format": "float32",
          "rate": "3.81 s",
          "recordings": 28,
          "grid": 0.1,
          "coarser_share": 0.348,
          "range": "0 to 15.8"
        },
        {
          "track": "FTp",
          "kind": "numeric",
          "format": "float32",
          "rate": "3.81 s",
          "recordings": 28,
          "grid": 1.0,
          "coarser_share": 0.286,
          "range": "0 to 2683"
        },
        {
          "track": "MA",
          "kind": "numeric",
          "format": "float32",
          "rate": "3.81 s",
          "recordings": 28,
          "grid": 0.01,
          "coarser_share": 0.339,
          "range": "0 to 191.76"
        },
        {
          "track": "PV",
          "kind": "numeric",
          "format": "float32",
          "rate": "3.81 s",
          "recordings": 28,
          "grid": 0.1,
          "coarser_share": 0.329,
          "range": "0 to 254.3"
        },
        {
          "track": "SVI",
          "kind": "numeric",
          "unit": "ml/m2",
          "format": "float32",
          "rate": "3.81 s",
          "recordings": 28,
          "grid": 0.1,
          "coarser_share": 0.34,
          "range": "0 to 844.3"
        }
      ],
      "name": "CardioQ",
      "summary": "Deltex CardioQ oesophageal Doppler: pressure and flow waveforms.",
      "type": "monitor",
      "clocks": [
        "cardioq-clock"
      ],
      "tags": [
        "stub"
      ],
      "docs": [
        {
          "title": "VitalDB connection guide: Deltex CardioQ",
          "url": "https://vitaldb.net/docs/?documentId=VitalRecorder%2Fconnection-guide%2Fdevices%2Fhemodynamic_monitors%2Fdeltex_cardioq.md"
        }
      ],
      "derived": {
        "variables": [
          "cardioq-abp"
        ],
        "studies": [
          "clock-correction-scan",
          "continuity-gaps-segments",
          "loop-closure",
          "rare-device-sync",
          "record-messages-and-steps",
          "step-classification",
          "track-value-resolution"
        ],
        "observations": [
          "cardioq-flow-segmented"
        ]
      }
    },
    "clinical-records": {
      "id": "clinical-records",
      "name": "Clinical records",
      "summary": "The clinical table and lab table of the release: anaesthesia record, lab results, admission.",
      "type": "other",
      "clocks": [
        "hospital-records"
      ],
      "tags": [
        "stub"
      ],
      "derived": {
        "variables": [
          "case-end",
          "admission-discharge",
          "emr-events",
          "lab-blood-gas",
          "lab-central"
        ],
        "studies": [
          "arterial-line-accesses",
          "case-clock",
          "emr-rounding",
          "lab-timestamp-meaning",
          "loop-closure"
        ],
        "observations": [
          "emr-offset-confounded",
          "emr-rounded-up",
          "event-track-generated",
          "icu-days-exceed-postop-stay",
          "imputed-op-ends",
          "lab-reporting-limits",
          "lab-time-is-specimen"
        ]
      }
    },
    "ev1000": {
      "id": "ev1000",
      "tracks": [
        {
          "track": "ART_MBP",
          "kind": "numeric",
          "unit": "mmHg",
          "format": "float32",
          "rate": "2 s",
          "recordings": 599,
          "grid": 1.0,
          "coarser_share": 0.201,
          "range": "22 to 232"
        },
        {
          "track": "CI",
          "kind": "numeric",
          "unit": "L/min/m2",
          "format": "float32",
          "rate": "2 s",
          "recordings": 599,
          "grid": 0.1,
          "coarser_share": 0.2,
          "range": "0 to 20"
        },
        {
          "track": "CO",
          "kind": "numeric",
          "unit": "L/min",
          "format": "float32",
          "rate": "2 s",
          "recordings": 599,
          "grid": 0.1,
          "coarser_share": 0.2,
          "range": "1 to 20"
        },
        {
          "track": "SV",
          "kind": "numeric",
          "unit": "ml/beat",
          "format": "float32",
          "rate": "2 s",
          "recordings": 599,
          "grid": 1.0,
          "coarser_share": 0.202,
          "range": "1 to 233"
        },
        {
          "track": "SVI",
          "kind": "numeric",
          "unit": "ml/beat/m2",
          "format": "float32",
          "rate": "2 s",
          "recordings": 599,
          "grid": 1.0,
          "coarser_share": 0.198,
          "range": "1 to 200"
        },
        {
          "track": "SVV",
          "kind": "numeric",
          "unit": "%",
          "format": "float32",
          "rate": "2 s",
          "recordings": 599,
          "grid": 0.1,
          "coarser_share": 0.2,
          "range": "0 to 75.6"
        },
        {
          "track": "SVR",
          "kind": "numeric",
          "unit": "dn-s/cm5",
          "format": "float32",
          "rate": "2 s",
          "recordings": 255,
          "grid": 1.0,
          "coarser_share": 0.198,
          "range": "10 to 4970"
        },
        {
          "track": "SVRI",
          "kind": "numeric",
          "unit": "dn-s-m2/cm5",
          "format": "float32",
          "rate": "2 s",
          "recordings": 255,
          "grid": 1.0,
          "coarser_share": 0.202,
          "range": "15 to 9876"
        },
        {
          "track": "CVP",
          "kind": "numeric",
          "unit": "mmHg",
          "format": "float32",
          "rate": "2 s",
          "recordings": 244,
          "grid": 1.0,
          "coarser_share": 0.198,
          "range": "0 to 50"
        }
      ],
      "name": "EV1000",
      "summary": "Edwards EV1000 haemodynamic platform.",
      "type": "monitor",
      "tags": [
        "stub"
      ],
      "docs": [
        {
          "title": "VitalDB connection guide: Edwards EV-1000",
          "url": "https://vitaldb.net/docs/?documentId=VitalRecorder%2Fconnection-guide%2Fdevices%2Fhemodynamic_monitors%2Fedwards_ev1000.md"
        }
      ],
      "derived": {
        "variables": [
          "ev1000-art-mbp"
        ],
        "studies": [
          "loop-closure",
          "rare-device-sync",
          "track-value-resolution"
        ]
      }
    },
    "fms": {
      "id": "fms",
      "tracks": [
        {
          "track": "FLOW_RATE",
          "kind": "numeric",
          "unit": "mL/min",
          "format": "float32",
          "rate": "0.896 s",
          "recordings": 15,
          "grid": 10.0,
          "coarser_share": 0.839,
          "range": "0 to 570"
        },
        {
          "track": "INPUT_AMB_TEMP",
          "kind": "numeric",
          "unit": "C",
          "format": "float32",
          "rate": "0.896 s",
          "recordings": 15,
          "grid": 0.1,
          "coarser_share": 0.182,
          "range": "22 to 580"
        },
        {
          "track": "INPUT_TEMP",
          "kind": "numeric",
          "unit": "C",
          "format": "float32",
          "rate": "0.896 s",
          "recordings": 15,
          "grid": 0.1,
          "coarser_share": 0.209,
          "range": "0 to 29"
        },
        {
          "track": "OUTPUT_AMB_TEMP",
          "kind": "numeric",
          "unit": "C",
          "format": "float32",
          "rate": "0.896 s",
          "recordings": 15,
          "grid": 0.1,
          "coarser_share": 0.197,
          "range": "21.1 to 34.6"
        },
        {
          "track": "OUTPUT_TEMP",
          "kind": "numeric",
          "unit": "C",
          "format": "float32",
          "rate": "0.896 s",
          "recordings": 15,
          "grid": 0.1,
          "coarser_share": 0.192,
          "range": "0 to 40.7"
        },
        {
          "track": "PRESSURE",
          "kind": "numeric",
          "unit": "mmHg",
          "format": "float32",
          "rate": "0.896 s",
          "recordings": 15,
          "grid": 1.0,
          "coarser_share": 0.474,
          "range": "0 to 125"
        },
        {
          "track": "TOTAL_VOL",
          "kind": "numeric",
          "unit": "mL",
          "format": "float32",
          "rate": "0.896 s",
          "recordings": 15,
          "grid": 1.0,
          "coarser_share": 0.195,
          "range": "0 to 39129"
        }
      ],
      "name": "FMS rapid infuser",
      "summary": "Belmont FMS 2000 rapid infuser.",
      "type": "other",
      "tags": [
        "stub"
      ],
      "docs": [
        {
          "title": "VitalDB connection guide: Belmont FMS (RI-2)",
          "url": "https://vitaldb.net/docs/?documentId=VitalRecorder%2Fconnection-guide%2Fdevices%2Fsyringe_pumps%2Fbelmont_fms.md"
        }
      ],
      "derived": {
        "variables": [
          "fms-infusion"
        ],
        "studies": [
          "drug-effect-latency"
        ]
      }
    },
    "invos": {
      "id": "invos",
      "tracks": [
        {
          "track": "SCO2_L",
          "kind": "numeric",
          "unit": "%",
          "format": "float32",
          "rate": "4.93 s",
          "recordings": 33,
          "grid": 1.0,
          "coarser_share": 0.221,
          "range": "0 to 95"
        },
        {
          "track": "SCO2_R",
          "kind": "numeric",
          "unit": "%",
          "format": "float32",
          "rate": "4.93 s",
          "recordings": 33,
          "grid": 1.0,
          "coarser_share": 0.215,
          "range": "0 to 95"
        }
      ],
      "name": "Invos",
      "summary": "Medtronic Invos cerebral oximeter.",
      "type": "monitor",
      "tags": [
        "stub"
      ],
      "docs": [
        {
          "title": "VitalDB connection guide: Medtronic INVOS",
          "url": "https://vitaldb.net/docs/?documentId=VitalRecorder%2Fconnection-guide%2Fdevices%2Fbrain_monitors%2Fmedtronic_invos.md"
        }
      ],
      "derived": {
        "variables": [
          "invos-rso2"
        ],
        "studies": [
          "drug-effect-latency"
        ]
      }
    },
    "orchestra": {
      "id": "orchestra",
      "tracks": [
        {
          "track": "RFTN20_VOL",
          "kind": "numeric",
          "unit": "mL",
          "format": "float32",
          "rate": "0.992 s",
          "recordings": 4774,
          "grid": 0.001,
          "coarser_share": 0.22,
          "range": "0 to 517.916",
          "note": "corrupt values to 3.69e+06"
        },
        {
          "track": "RFTN20_RATE",
          "kind": "numeric",
          "unit": "mL/h",
          "format": "float32",
          "rate": "0.992 s",
          "recordings": 4773,
          "grid": 0.001,
          "coarser_share": 0.364,
          "range": "0 to 1200",
          "note": "corrupt values to 5.39e+05"
        },
        {
          "track": "RFTN20_CE",
          "kind": "numeric",
          "format": "float32",
          "rate": "0.992 s",
          "recordings": 4771,
          "grid": 0.001,
          "coarser_share": 0.173,
          "range": "0 to 17.264",
          "note": "corrupt values to 9.6e+05"
        },
        {
          "track": "RFTN20_CP",
          "kind": "numeric",
          "format": "float32",
          "rate": "0.992 s",
          "recordings": 4771,
          "grid": 0.001,
          "coarser_share": 0.22,
          "range": "0 to 22.495",
          "note": "corrupt values to 9.59e+05"
        },
        {
          "track": "RFTN20_CT",
          "kind": "numeric",
          "format": "float32",
          "rate": "0.992 s",
          "recordings": 4771,
          "grid": 0.1,
          "coarser_share": 0.873,
          "range": "0 to 18",
          "note": "corrupt values to 3.96e+06"
        },
        {
          "track": "PPF20_RATE",
          "kind": "numeric",
          "unit": "mL/h",
          "format": "float32",
          "rate": "0.992 s",
          "recordings": 3512,
          "grid": 0.001,
          "coarser_share": 0.336,
          "range": "0 to 1200",
          "note": "corrupt values to 1.16e+06"
        },
        {
          "track": "PPF20_VOL",
          "kind": "numeric",
          "unit": "mL",
          "format": "float32",
          "rate": "0.992 s",
          "recordings": 3512,
          "grid": 0.001,
          "coarser_share": 0.223,
          "range": "0 to 418.945",
          "note": "corrupt values to 4.09e+06"
        },
        {
          "track": "PPF20_CE",
          "kind": "numeric",
          "format": "float32",
          "rate": "0.992 s",
          "recordings": 3511,
          "grid": 0.001,
          "coarser_share": 0.247,
          "range": "0 to 13.226",
          "note": "corrupt values to 9.6e+05"
        },
        {
          "track": "PPF20_CP",
          "kind": "numeric",
          "format": "float32",
          "rate": "0.992 s",
          "recordings": 3511,
          "grid": 0.001,
          "coarser_share": 0.254,
          "range": "0 to 15.001",
          "note": "corrupt values to 1.31e+06"
        },
        {
          "track": "PPF20_CT",
          "kind": "numeric",
          "format": "float32",
          "rate": "0.992 s",
          "recordings": 3511,
          "grid": 0.1,
          "coarser_share": 0.821,
          "range": "0 to 15",
          "note": "corrupt values to 1.18e+06"
        },
        {
          "track": "ROC_RATE",
          "kind": "numeric",
          "unit": "mL/h",
          "format": "float32",
          "rate": "0.992 s",
          "recordings": 281,
          "grid": 0.1,
          "coarser_share": 0.672,
          "range": "0 to 1200",
          "note": "corrupt values to 1.18e+06"
        },
        {
          "track": "ROC_VOL",
          "kind": "numeric",
          "unit": "mL",
          "format": "float32",
          "rate": "0.992 s",
          "recordings": 281,
          "grid": 0.001,
          "coarser_share": 0.201,
          "range": "0 to 146.531",
          "note": "corrupt values to 8.25e+05"
        },
        {
          "track": "PHEN_RATE",
          "kind": "numeric",
          "unit": "mL/h",
          "format": "float32",
          "rate": "0.992 s",
          "recordings": 127,
          "grid": 0.1,
          "coarser_share": 0.591,
          "range": "0 to 1200"
        },
        {
          "track": "PHEN_VOL",
          "kind": "numeric",
          "unit": "mL",
          "format": "float32",
          "rate": "0.992 s",
          "recordings": 127,
          "grid": 0.001,
          "coarser_share": 0.202,
          "range": "0 to 143.177",
          "note": "corrupt values to 2.32e+06"
        },
        {
          "track": "FUT_RATE",
          "kind": "numeric",
          "unit": "mL/h",
          "format": "float32",
          "rate": "0.991 s",
          "recordings": 94,
          "grid": 0.1,
          "coarser_share": 0.775,
          "range": "0 to 55"
        },
        {
          "track": "FUT_VOL",
          "kind": "numeric",
          "unit": "mL",
          "format": "float32",
          "rate": "0.991 s",
          "recordings": 94,
          "grid": 0.001,
          "coarser_share": 0.213,
          "range": "0 to 66.814"
        },
        {
          "track": "PGE1_RATE",
          "kind": "numeric",
          "unit": "mL/h",
          "format": "float32",
          "rate": "0.991 s",
          "recordings": 90,
          "grid": 0.1,
          "coarser_share": 0.712,
          "range": "0 to 34"
        },
        {
          "track": "PGE1_VOL",
          "kind": "numeric",
          "unit": "mL",
          "format": "float32",
          "rate": "0.991 s",
          "recordings": 90,
          "grid": 0.001,
          "coarser_share": 0.203,
          "range": "0 to 161.944"
        },
        {
          "track": "NEPI_RATE",
          "kind": "numeric",
          "unit": "mL/h",
          "format": "float32",
          "rate": "1.01 s",
          "recordings": 88,
          "grid": 0.1,
          "coarser_share": 0.461,
          "range": "0 to 100"
        },
        {
          "track": "NEPI_VOL",
          "kind": "numeric",
          "unit": "mL",
          "format": "float32",
          "rate": "1.01 s",
          "recordings": 88,
          "grid": 0.001,
          "coarser_share": 0.201,
          "range": "0 to 752.211"
        },
        {
          "track": "RFTN50_CE",
          "kind": "numeric",
          "format": "float32",
          "rate": "0.992 s",
          "recordings": 69,
          "grid": 0.001,
          "coarser_share": 0.194,
          "range": "0 to 18.721"
        },
        {
          "track": "RFTN50_CP",
          "kind": "numeric",
          "format": "float32",
          "rate": "0.992 s",
          "recordings": 69,
          "grid": 0.001,
          "coarser_share": 0.212,
          "range": "0 to 38.653"
        },
        {
          "track": "RFTN50_CT",
          "kind": "numeric",
          "format": "float32",
          "rate": "0.992 s",
          "recordings": 69,
          "grid": 0.1,
          "coarser_share": 0.851,
          "range": "0 to 30"
        },
        {
          "track": "RFTN50_RATE",
          "kind": "numeric",
          "unit": "mL/h",
          "format": "float32",
          "rate": "0.992 s",
          "recordings": 69,
          "grid": 0.001,
          "coarser_share": 0.317,
          "range": "0 to 1200"
        },
        {
          "track": "RFTN50_VOL",
          "kind": "numeric",
          "unit": "mL",
          "format": "float32",
          "rate": "0.992 s",
          "recordings": 69,
          "grid": 0.001,
          "coarser_share": 0.206,
          "range": "0 to 155.684"
        },
        {
          "track": "DOPA_RATE",
          "kind": "numeric",
          "unit": "mL/h",
          "format": "float32",
          "rate": "1.01 s",
          "recordings": 33,
          "grid": 0.1,
          "coarser_share": 0.587,
          "range": "0 to 1200"
        },
        {
          "track": "DOPA_VOL",
          "kind": "numeric",
          "unit": "mL",
          "format": "float32",
          "rate": "1.01 s",
          "recordings": 33,
          "grid": 0.001,
          "coarser_share": 0.203,
          "range": "0 to 175.896",
          "note": "corrupt values to 2.18e+06"
        },
        {
          "track": "NTG_RATE",
          "kind": "numeric",
          "unit": "mL/h",
          "format": "float32",
          "rate": "0.993 s",
          "recordings": 32,
          "grid": 0.1,
          "coarser_share": 0.379,
          "range": "0 to 15.1"
        },
        {
          "track": "NTG_VOL",
          "kind": "numeric",
          "unit": "mL",
          "format": "float32",
          "rate": "0.993 s",
          "recordings": 32,
          "grid": 0.001,
          "coarser_share": 0.202,
          "range": "0 to 47.213"
        },
        {
          "track": "EPI_RATE",
          "kind": "numeric",
          "unit": "mL/h",
          "format": "float32",
          "rate": "1.01 s",
          "recordings": 9,
          "grid": 0.1,
          "coarser_share": 0.729,
          "range": "0 to 120"
        },
        {
          "track": "EPI_VOL",
          "kind": "numeric",
          "unit": "mL",
          "format": "float32",
          "rate": "1.01 s",
          "recordings": 9,
          "grid": 0.001,
          "coarser_share": 0.201,
          "range": "0 to 84.294"
        },
        {
          "track": "DEX2_RATE",
          "kind": "numeric",
          "unit": "mL/h",
          "format": "float32",
          "rate": "0.993 s",
          "recordings": 6,
          "grid": 0.1,
          "coarser_share": 0.939,
          "range": "0 to 180"
        },
        {
          "track": "DEX2_VOL",
          "kind": "numeric",
          "unit": "mL",
          "format": "float32",
          "rate": "0.993 s",
          "recordings": 6,
          "grid": 0.001,
          "coarser_share": 0.211,
          "range": "0 to 42.187"
        },
        {
          "track": "MRN_RATE",
          "kind": "numeric",
          "unit": "mL/h",
          "format": "float32",
          "rate": "1.01 s",
          "recordings": 5,
          "grid": 0.1,
          "coarser_share": 0.118,
          "range": "0 to 30"
        },
        {
          "track": "MRN_VOL",
          "kind": "numeric",
          "unit": "mL",
          "format": "float32",
          "rate": "1.01 s",
          "recordings": 5,
          "grid": 0.001,
          "coarser_share": 0.199,
          "range": "0 to 37.987"
        },
        {
          "track": "DEX4_RATE",
          "kind": "numeric",
          "unit": "mL/h",
          "format": "float32",
          "rate": "0.991 s",
          "recordings": 4,
          "grid": 0.1,
          "coarser_share": 0.001,
          "range": "0 to 1.7"
        },
        {
          "track": "DEX4_VOL",
          "kind": "numeric",
          "unit": "mL",
          "format": "float32",
          "rate": "0.991 s",
          "recordings": 4,
          "grid": 0.001,
          "coarser_share": 0.202,
          "range": "0 to 14.287"
        },
        {
          "track": "DOBU_RATE",
          "kind": "numeric",
          "unit": "mL/h",
          "format": "float32",
          "rate": "1.01 s",
          "recordings": 3,
          "grid": 0.1,
          "coarser_share": 0.031,
          "range": "0 to 24"
        },
        {
          "track": "DOBU_VOL",
          "kind": "numeric",
          "unit": "mL",
          "format": "float32",
          "rate": "1.01 s",
          "recordings": 3,
          "grid": 0.001,
          "coarser_share": 0.201,
          "range": "0 to 12.831"
        },
        {
          "track": "OXY_RATE",
          "kind": "numeric",
          "unit": "mL/h",
          "format": "float32",
          "rate": "1.01 s",
          "recordings": 3,
          "grid": 5.0,
          "coarser_share": 0.695,
          "range": "0 to 70"
        },
        {
          "track": "OXY_VOL",
          "kind": "numeric",
          "unit": "mL",
          "format": "float32",
          "rate": "1.01 s",
          "recordings": 3,
          "grid": 0.001,
          "coarser_share": 0.203,
          "range": "0 to 38.82"
        },
        {
          "track": "DTZ_RATE",
          "kind": "numeric",
          "unit": "mL/h",
          "format": "float32",
          "rate": "0.999 s",
          "recordings": 2,
          "grid": 0.1,
          "coarser_share": 0.32,
          "range": "0 to 200"
        },
        {
          "track": "DTZ_VOL",
          "kind": "numeric",
          "unit": "mL",
          "format": "float32",
          "rate": "0.999 s",
          "recordings": 2,
          "grid": 0.001,
          "coarser_share": 0.214,
          "range": "0 to 38.528"
        },
        {
          "track": "AMD_RATE",
          "kind": "numeric",
          "unit": "mL/h",
          "format": "float32",
          "rate": "0.991 s",
          "recordings": 1,
          "grid": 0.1,
          "coarser_share": 0.973,
          "range": "0 to 20"
        },
        {
          "track": "AMD_VOL",
          "kind": "numeric",
          "unit": "mL",
          "format": "float32",
          "rate": "0.991 s",
          "recordings": 1,
          "grid": 0.001,
          "coarser_share": 0.2,
          "range": "0 to 61.383"
        },
        {
          "track": "NPS_RATE",
          "kind": "numeric",
          "unit": "mL/h",
          "format": "float32",
          "rate": "0.995 s",
          "recordings": 1,
          "grid": 0.1,
          "coarser_share": 0.001,
          "range": "0 to 12.7"
        },
        {
          "track": "NPS_VOL",
          "kind": "numeric",
          "unit": "mL",
          "format": "float32",
          "rate": "0.995 s",
          "recordings": 1,
          "grid": 0.001,
          "coarser_share": 0.207,
          "range": "0 to 3.951"
        },
        {
          "track": "VASO_RATE",
          "kind": "numeric",
          "unit": "mL/h",
          "format": "float32",
          "rate": "1.01 s",
          "recordings": 1,
          "grid": 0.1,
          "coarser_share": 0.0,
          "range": "0.1 to 12.1"
        },
        {
          "track": "VASO_VOL",
          "kind": "numeric",
          "unit": "mL",
          "format": "float32",
          "rate": "1.01 s",
          "recordings": 1,
          "grid": 0.001,
          "coarser_share": 0.198,
          "range": "0.326 to 9.037"
        },
        {
          "track": "VEC_RATE",
          "kind": "numeric",
          "unit": "mL/h",
          "format": "float32",
          "rate": "0.992 s",
          "recordings": 1,
          "grid": 0.1,
          "coarser_share": 0.0,
          "range": "0 to 3.1"
        },
        {
          "track": "VEC_VOL",
          "kind": "numeric",
          "unit": "mL",
          "format": "float32",
          "rate": "0.992 s",
          "recordings": 1,
          "grid": 0.001,
          "coarser_share": 0.202,
          "range": "0.07 to 22.009"
        }
      ],
      "name": "Infusion pumps",
      "summary": "Fresenius Orchestra infusion pumps.",
      "type": "other",
      "tags": [
        "stub"
      ],
      "docs": [
        {
          "title": "VitalDB connection guide: Fresenius Vial Orchestra",
          "url": "https://vitaldb.net/docs/?documentId=VitalRecorder%2Fconnection-guide%2Fdevices%2Fsyringe_pumps%2Ffresenius_orchestra.md"
        }
      ],
      "derived": {
        "variables": [
          "pump-rates"
        ],
        "studies": [
          "drug-effect-latency",
          "emr-rounding",
          "track-value-resolution"
        ]
      }
    },
    "pacemaker": {
      "id": "pacemaker",
      "name": "Implanted pacemaker",
      "summary": "A patient's own pacemaker; at a fixed rate its pacing spikes are an independent clock in the ECG.",
      "type": "implant",
      "clocks": [
        "pacemaker"
      ],
      "derived": {
        "parameters": [
          "pacemaker-interval-agreement"
        ],
        "studies": [
          "paced-ecg"
        ]
      }
    },
    "primus": {
      "id": "primus",
      "tracks": [
        {
          "track": "AWP",
          "kind": "waveform",
          "unit": "hPa",
          "format": "uint16",
          "rate": "62.5 Hz",
          "gain": 0.1,
          "offset": -20.0,
          "recordings": 5384,
          "step": "0.1",
          "span": "codes 0–1032 (1,010 used); 22 corrupt samples outside",
          "bits_to_store": 11,
          "converter": "probably 12 bits",
          "evidence": "rare values up to code 4046 in CO2 and AWP, inside 0–4095"
        },
        {
          "track": "AWP",
          "kind": "waveform",
          "unit": "hPa",
          "format": "uint16",
          "rate": "62.5 Hz",
          "gain": 0.1064,
          "offset": -22.0,
          "recordings": 950,
          "step": "0.1 (corrected)",
          "span": "codes 0–1032 (1,010 used); 22 corrupt samples outside",
          "bits_to_store": 11,
          "converter": "probably 12 bits",
          "evidence": "rare values up to code 4046 in CO2 and AWP, inside 0–4095",
          "corrected_gain": 0.1,
          "corrected_offset": -20.0,
          "correction": "PIP check ([[observation:primus-waveform-gains|evidence]])"
        },
        {
          "track": "AWP",
          "kind": "waveform",
          "unit": "hPa",
          "format": "uint16",
          "rate": "62.5 Hz",
          "gain": 0.01,
          "offset": 0.0,
          "recordings": 26,
          "step": "0.1 (corrected)",
          "span": "codes 0–1032 (1,010 used); 22 corrupt samples outside",
          "bits_to_store": 11,
          "converter": "probably 12 bits",
          "evidence": "rare values up to code 4046 in CO2 and AWP, inside 0–4095",
          "corrected_gain": 0.1,
          "corrected_offset": -20.0,
          "correction": "PIP check ([[observation:primus-waveform-gains|evidence]])"
        },
        {
          "track": "CO2",
          "kind": "waveform",
          "unit": "mmHg",
          "format": "uint16",
          "rate": "62.5 Hz",
          "gain": 0.1,
          "offset": 0.0,
          "recordings": 5412,
          "step": "0.075 (corrected)",
          "span": "codes 0–1564 (1,530 used); 8 corrupt samples outside",
          "bits_to_store": 11,
          "converter": "probably 12 bits",
          "evidence": "rare values up to code 4046 in CO2 and AWP, inside 0–4095",
          "corrected_gain": 0.075,
          "corrected_offset": 0.0,
          "correction": "ETCO2 check ([[observation:primus-waveform-gains|evidence]])"
        },
        {
          "track": "CO2",
          "kind": "waveform",
          "unit": "mmHg",
          "format": "uint16",
          "rate": "62.5 Hz",
          "gain": 0.075,
          "offset": 0.0,
          "recordings": 950,
          "step": "0.075",
          "span": "codes 0–1564 (1,530 used); 8 corrupt samples outside",
          "bits_to_store": 11,
          "converter": "probably 12 bits",
          "evidence": "rare values up to code 4046 in CO2 and AWP, inside 0–4095"
        },
        {
          "track": "PAMB_MBAR",
          "kind": "numeric",
          "unit": "mbar",
          "format": "float32",
          "rate": "7.01 s",
          "recordings": 6361,
          "grid": 1.0,
          "coarser_share": 0.201,
          "range": "971 to 1030"
        },
        {
          "track": "SET_AGE",
          "kind": "numeric",
          "format": "float32",
          "rate": "7.01 s",
          "recordings": 6361,
          "grid": 1.0,
          "coarser_share": 0.213,
          "range": "0 to 94"
        },
        {
          "track": "VENT_LEAK",
          "kind": "numeric",
          "unit": "mL/min",
          "format": "float32",
          "rate": "7.01 s",
          "recordings": 6349,
          "grid": 1.0,
          "coarser_share": 0.221,
          "range": "5 to 394"
        },
        {
          "track": "ETCO2",
          "kind": "numeric",
          "unit": "mmHg",
          "format": "float32",
          "rate": "7.02 s",
          "recordings": 6342,
          "grid": 1.0,
          "coarser_share": 0.222,
          "rounding": "round, likely ([[study:track-value-resolution]])",
          "range": "0 to 86"
        },
        {
          "track": "INCO2",
          "kind": "numeric",
          "unit": "mmHg",
          "format": "float32",
          "rate": "7.02 s",
          "recordings": 6342,
          "grid": 1.0,
          "coarser_share": 0.226,
          "range": "0 to 48"
        },
        {
          "track": "MAWP_MBAR",
          "kind": "numeric",
          "unit": "mbar",
          "format": "float32",
          "rate": "7.02 s",
          "recordings": 6342,
          "grid": 1.0,
          "coarser_share": 0.192,
          "rounding": "round, likely ([[study:track-value-resolution]])",
          "range": "-6 to 33",
          "note": "corrupt values to 4e+03"
        },
        {
          "track": "FEN2O",
          "kind": "numeric",
          "unit": "%",
          "format": "float32",
          "rate": "7.02 s",
          "recordings": 6339,
          "grid": 100.0,
          "range": "0 to 55"
        },
        {
          "track": "FIN2O",
          "kind": "numeric",
          "unit": "%",
          "format": "float32",
          "rate": "7.02 s",
          "recordings": 6339,
          "grid": 100.0,
          "range": "0 to 66"
        },
        {
          "track": "MAC",
          "kind": "numeric",
          "format": "float32",
          "rate": "7.02 s",
          "recordings": 6339,
          "grid": 0.1,
          "coarser_share": 0.702,
          "range": "0 to 4"
        },
        {
          "track": "FEO2",
          "kind": "numeric",
          "unit": "%",
          "format": "float32",
          "rate": "7.02 s",
          "recordings": 6337,
          "grid": 1.0,
          "coarser_share": 0.242,
          "range": "12 to 100"
        },
        {
          "track": "FIO2",
          "kind": "numeric",
          "unit": "%",
          "format": "float32",
          "rate": "7.02 s",
          "recordings": 6337,
          "grid": 1.0,
          "coarser_share": 0.242,
          "range": "16 to 100"
        },
        {
          "track": "RR_CO2",
          "kind": "numeric",
          "unit": "/min",
          "format": "float32",
          "rate": "7.02 s",
          "recordings": 6333,
          "grid": 1.0,
          "coarser_share": 0.213,
          "rounding": "round ([[study:numeric-derivation-pilot]])",
          "range": "0 to 55"
        },
        {
          "track": "MV",
          "kind": "numeric",
          "unit": "L",
          "format": "float32",
          "rate": "7.02 s",
          "recordings": 6085,
          "grid": 0.1,
          "coarser_share": 0.205,
          "range": "0 to 20"
        },
        {
          "track": "SET_FIO2",
          "kind": "numeric",
          "unit": "%",
          "format": "float32",
          "rate": "7.02 s",
          "recordings": 6047,
          "grid": 10.0,
          "coarser_share": 0.141,
          "range": "21 to 100"
        },
        {
          "track": "SET_FRESH_FLOW",
          "kind": "numeric",
          "unit": "mL/min",
          "format": "float32",
          "rate": "7.03 s",
          "recordings": 6047,
          "grid": 100.0,
          "range": "200 to 18000"
        },
        {
          "track": "PIP_MBAR",
          "kind": "numeric",
          "unit": "mbar",
          "format": "float32",
          "rate": "7.02 s",
          "recordings": 6025,
          "grid": 1.0,
          "coarser_share": 0.206,
          "rounding": "round, likely ([[study:track-value-resolution]])",
          "range": "-2 to 57"
        },
        {
          "track": "TV",
          "kind": "numeric",
          "unit": "mL",
          "format": "float32",
          "rate": "7.02 s",
          "recordings": 6025,
          "grid": 1.0,
          "coarser_share": 0.2,
          "range": "7 to 2450"
        },
        {
          "track": "COMPLIANCE",
          "kind": "numeric",
          "unit": "mL / mbar",
          "format": "float32",
          "rate": "7.02 s",
          "recordings": 6020,
          "grid": 0.1,
          "coarser_share": 0.2,
          "range": "0.5 to 97.3"
        },
        {
          "track": "PEEP_MBAR",
          "kind": "numeric",
          "unit": "mbar",
          "format": "float32",
          "rate": "7.02 s",
          "recordings": 6003,
          "grid": 1.0,
          "coarser_share": 0.839,
          "range": "-2 to 19"
        },
        {
          "track": "PPLAT_MBAR",
          "kind": "numeric",
          "unit": "mbar",
          "format": "float32",
          "rate": "7.02 s",
          "recordings": 6003,
          "grid": 1.0,
          "coarser_share": 0.208,
          "range": "0 to 49"
        },
        {
          "track": "SET_INSP_TM",
          "kind": "numeric",
          "unit": "sec",
          "format": "float32",
          "rate": "7.04 s",
          "recordings": 5982,
          "grid": 0.1,
          "coarser_share": 0.215,
          "range": "0.6 to 3.5"
        },
        {
          "track": "SET_INTER_PEEP",
          "kind": "numeric",
          "unit": "mbar",
          "format": "float32",
          "rate": "7.04 s",
          "recordings": 5982,
          "grid": 1.0,
          "coarser_share": 0.965,
          "range": "0 to 13"
        },
        {
          "track": "SET_RR_IPPV",
          "kind": "numeric",
          "unit": "/min",
          "format": "float32",
          "rate": "7.04 s",
          "recordings": 5982,
          "grid": 1.0,
          "coarser_share": 0.213,
          "range": "7 to 34"
        },
        {
          "track": "SET_INSP_PAUSE",
          "kind": "numeric",
          "unit": "%",
          "format": "float32",
          "rate": "7.04 s",
          "recordings": 5973,
          "grid": 10.0,
          "coarser_share": 0.005,
          "range": "0 to 20"
        },
        {
          "track": "SET_PIP",
          "kind": "numeric",
          "unit": "mbar",
          "format": "float32",
          "rate": "7.04 s",
          "recordings": 5973,
          "grid": 1.0,
          "coarser_share": 0.813,
          "range": "14 to 61"
        },
        {
          "track": "SET_TV_L",
          "kind": "numeric",
          "unit": "L",
          "format": "float32",
          "rate": "7.04 s",
          "recordings": 5973,
          "grid": 0.01,
          "coarser_share": 0.411,
          "range": "0.03 to 0.82"
        },
        {
          "track": "FLOW_AIR",
          "kind": "numeric",
          "unit": "mL / min",
          "format": "float32",
          "rate": "7.01 s",
          "recordings": 5645,
          "grid": 1.0,
          "coarser_share": 0.412,
          "range": "0 to 9999"
        },
        {
          "track": "FLOW_O2",
          "kind": "numeric",
          "unit": "mL / min",
          "format": "float32",
          "rate": "7.01 s",
          "recordings": 5644,
          "grid": 1.0,
          "coarser_share": 0.337,
          "range": "0 to 9999"
        },
        {
          "track": "FLOW_N2O",
          "kind": "numeric",
          "unit": "mL/min",
          "format": "float32",
          "rate": "7.01 s",
          "recordings": 5643,
          "grid": 100.0,
          "range": "0 to 2926"
        },
        {
          "track": "EXP_SEVO",
          "kind": "numeric",
          "unit": "kPa",
          "format": "float32",
          "rate": "7.02 s",
          "recordings": 3733,
          "grid": 0.1,
          "coarser_share": 0.57,
          "range": "0 to 7.1"
        },
        {
          "track": "INSP_SEVO",
          "kind": "numeric",
          "unit": "kPa",
          "format": "float32",
          "rate": "7.02 s",
          "recordings": 3733,
          "grid": 0.1,
          "coarser_share": 0.596,
          "range": "0 to 7.3"
        },
        {
          "track": "EXP_DES",
          "kind": "numeric",
          "unit": "kPa",
          "format": "float32",
          "rate": "7.02 s",
          "recordings": 2156,
          "grid": 0.1,
          "coarser_share": 0.679,
          "range": "0 to 11"
        },
        {
          "track": "INSP_DES",
          "kind": "numeric",
          "unit": "kPa",
          "format": "float32",
          "rate": "7.02 s",
          "recordings": 2156,
          "grid": 0.1,
          "coarser_share": 0.705,
          "range": "0 to 12.8"
        },
        {
          "track": "SET_INSP_PRES",
          "kind": "numeric",
          "unit": "mbar",
          "format": "float32",
          "rate": "7.08 s",
          "recordings": 431,
          "grid": 1.0,
          "coarser_share": 0.303,
          "range": "5 to 60"
        },
        {
          "track": "SET_FLOW_TRIG",
          "kind": "numeric",
          "unit": "L/min",
          "format": "float32",
          "rate": "7.14 s",
          "recordings": 247,
          "grid": 0.1,
          "coarser_share": 0.98,
          "range": "0.3 to 15"
        }
      ],
      "name": "Primus anaesthesia machine",
      "summary": "Dräger Primus: airway pressure and CO2 waveforms at 62.5 Hz and ventilation numerics.",
      "type": "ventilator",
      "clocks": [
        "primus-clock"
      ],
      "tags": [
        "stub"
      ],
      "docs": [
        {
          "title": "VitalDB connection guide: Dräger Primus",
          "url": "https://vitaldb.net/docs/?documentId=VitalRecorder%2Fconnection-guide%2Fdevices%2Fanesthesia_machines%2Fdrager_primus.md"
        }
      ],
      "derived": {
        "variables": [
          "primus-awp",
          "primus-co2",
          "primus-numerics"
        ],
        "studies": [
          "airway-heartbeat-pilot",
          "clock-correction-scan",
          "continuity-gaps-segments",
          "correction-periods",
          "device-correction-schedules",
          "emr-rounding",
          "loop-closure",
          "numeric-delay-variability",
          "numeric-derivation-pilot",
          "primus-pause-steps",
          "record-messages-and-steps",
          "recorder-pc-steps",
          "setup-identification",
          "step-classification",
          "track-value-resolution",
          "ventilator-breath-matching"
        ],
        "observations": [
          "co2-cardiac-trace",
          "duplicated-records",
          "numeric-delays-per-case",
          "numerics-not-front-stamped",
          "primus-awp-pauses",
          "primus-waveform-gains",
          "solar-relays-primus"
        ]
      }
    },
    "snuadc": {
      "id": "snuadc",
      "tracks": [
        {
          "track": "ART",
          "kind": "waveform",
          "unit": "mmHg",
          "format": "uint16",
          "rate": "500 Hz",
          "gain": 0.987457,
          "offset": -495.626,
          "recordings": 3504,
          "step": "0.987457",
          "span": "codes 0–983 (848 used)",
          "bits_to_store": 10,
          "converter": "10 bits",
          "evidence": "the SNUADC converter (see ECG)"
        },
        {
          "track": "ART",
          "kind": "waveform",
          "unit": "mmHg",
          "format": "uint16",
          "rate": "500 Hz",
          "gain": 0.394983,
          "offset": -198.25,
          "recordings": 3,
          "step": "0.987457 (corrected)",
          "span": "codes 0–983 (848 used)",
          "bits_to_store": 10,
          "converter": "10 bits",
          "evidence": "the SNUADC converter (see ECG)",
          "corrected_gain": 0.987457,
          "corrected_offset": -495.626,
          "correction": "PLETH factors ([[observation:art-with-pleth-factors|evidence]])"
        },
        {
          "track": "CVP",
          "kind": "waveform",
          "unit": "cmH2O",
          "format": "uint16",
          "rate": "500 Hz",
          "gain": 0.987457,
          "offset": -495.626,
          "recordings": 1363,
          "step": "0.987457",
          "span": "codes 0–944 (874 used)",
          "bits_to_store": 10,
          "converter": "10 bits",
          "evidence": "the SNUADC converter (see ECG)"
        },
        {
          "track": "ECG_II",
          "kind": "waveform",
          "unit": "mV",
          "format": "uint16",
          "rate": "500 Hz",
          "gain": 0.00987457,
          "offset": -4.95626,
          "recordings": 6349,
          "step": "0.00987457",
          "span": "codes 0–1023 (1,024 used)",
          "bits_to_store": 10,
          "converter": "10 bits",
          "evidence": "every code of 0–1023 used by ECG, none above; documented as 12-bit ([[observation:snuadc-ten-bit-codes]])"
        },
        {
          "track": "ECG_II",
          "kind": "waveform",
          "unit": "mV",
          "format": "uint16",
          "rate": "500 Hz",
          "gain": 0.009875,
          "offset": -4.95626,
          "recordings": 1,
          "step": "0.009875",
          "span": "codes 0–1023 (1,024 used)",
          "bits_to_store": 10,
          "converter": "10 bits",
          "evidence": "every code of 0–1023 used by ECG, none above; documented as 12-bit ([[observation:snuadc-ten-bit-codes]])"
        },
        {
          "track": "ECG_V5",
          "kind": "waveform",
          "unit": "mV",
          "format": "uint16",
          "rate": "500 Hz",
          "gain": 0.00987457,
          "offset": -4.95626,
          "recordings": 3380,
          "step": "0.00987457",
          "span": "codes 0–1023 (1,024 used)",
          "bits_to_store": 10,
          "converter": "10 bits",
          "evidence": "every code of 0–1023 used by ECG, none above; documented as 12-bit ([[observation:snuadc-ten-bit-codes]])"
        },
        {
          "track": "FEM",
          "kind": "waveform",
          "unit": "mmHg",
          "format": "uint16",
          "rate": "500 Hz",
          "gain": 0.987457,
          "offset": -495.626,
          "recordings": 100,
          "step": "0.987457",
          "span": "codes 0–925 (842 used)",
          "bits_to_store": 10,
          "converter": "10 bits",
          "evidence": "the SNUADC converter (see ECG)"
        },
        {
          "track": "PLETH",
          "kind": "waveform",
          "format": "uint16",
          "rate": "500 Hz",
          "gain": 0.394983,
          "offset": -198.25,
          "recordings": 6153,
          "step": "0.394983",
          "span": "codes 0–1023 (852 used)",
          "bits_to_store": 10,
          "converter": "10 bits",
          "evidence": "the SNUADC converter (see ECG)"
        }
      ],
      "name": "SNUADC",
      "summary": "The analogue-to-digital converter board that samples the monitor's analogue outputs at 500 Hz.",
      "type": "acquisition",
      "clocks": [
        "snuadc-sample"
      ],
      "description": "SNUADC records the monitor's analogue waveform outputs (ECG leads, arterial pressure, plethysmogram, CVP) and\nsends them to Vital Recorder in 0.2 s records of 100 samples. Its records are stamped by the recorder, which\nmodels the board's timing (see [[clock:snuadc-sample]]).\n",
      "references": [
        "lee2022vitaldb"
      ],
      "docs": [
        {
          "title": "VitalRecorder supported devices: VitalDB devices (SNUADC)",
          "url": "https://vitaldb.net/docs/?documentId=VitalRecorder%2FSupported_Devices.md"
        },
        {
          "title": "VitalRecorder configuration guide: ADC device settings",
          "url": "https://vitaldb.net/docs/?documentId=VitalRecorder%2FConfiguration_Guide.md"
        }
      ],
      "derived": {
        "variables": [
          "snuadc-art",
          "snuadc-ecg-ii",
          "snuadc-pleth"
        ],
        "studies": [
          "airway-heartbeat-pilot",
          "arterial-line-accesses",
          "bis-cardiac-artefact",
          "clock-correction-scan",
          "continuity-gaps-segments",
          "correction-periods",
          "device-correction-schedules",
          "drift-units",
          "drug-effect-latency",
          "lab-timestamp-meaning",
          "loop-closure",
          "monitor-lines",
          "numeric-clock-check",
          "paced-ecg",
          "pat-offset-census",
          "ppg-art-reference",
          "ppg-frame-clock",
          "ppg-offset-reference",
          "ppg-sawtooth-pauses",
          "ppg-sawtooth",
          "primus-pause-steps",
          "rare-device-sync",
          "record-messages-and-steps",
          "recorder-pc-steps",
          "reset-cluster-order",
          "setup-identification",
          "shared-bursts",
          "snuadc-firmware",
          "snuadc-stamp-sawtooth",
          "solar-snuadc-clock",
          "stamp-arithmetic",
          "step-classification",
          "time-zeros",
          "track-value-resolution",
          "ventilator-breath-matching",
          "vitaldb-loader"
        ],
        "observations": [
          "art-with-pleth-factors",
          "bis-eeg-cardiac-artefact",
          "cuff-occludes-line",
          "device-drift-by-setup",
          "duplicated-records",
          "eeg-ecg-offset-by-mode",
          "interleaved-records",
          "loaders-lose-samples",
          "oximeter-frame-clock-fingerprint",
          "pleth-30hz-frames",
          "ppg-sawtooth",
          "reset-records-out-of-order",
          "reset-signal-lost",
          "reset-zero-sample",
          "small-snuadc-gaps",
          "snuadc-100hz-cases",
          "snuadc-channel-skew",
          "snuadc-event-inputs",
          "snuadc-long-uncorrected",
          "snuadc-mode-setups",
          "snuadc-no-timestamps",
          "snuadc-one-clock",
          "snuadc-overlapping-records",
          "snuadc-stamp-arithmetic",
          "snuadc-ten-bit-codes",
          "snuadc-value-truncation"
        ]
      }
    },
    "solar8000": {
      "id": "solar8000",
      "tracks": [
        {
          "track": "HR",
          "kind": "numeric",
          "unit": "/min",
          "format": "float32",
          "rate": "2 s",
          "recordings": 6388,
          "grid": 1.0,
          "coarser_share": 0.207,
          "rounding": "truncate ([[observation:hr-truncated]])",
          "range": "0 to 223"
        },
        {
          "track": "PLETH_HR",
          "kind": "numeric",
          "format": "float32",
          "rate": "2 s",
          "recordings": 6387,
          "grid": 1.0,
          "coarser_share": 0.198,
          "range": "6 to 300"
        },
        {
          "track": "PLETH_SPO2",
          "kind": "numeric",
          "format": "float32",
          "rate": "2 s",
          "recordings": 6387,
          "grid": 1.0,
          "coarser_share": 0.743,
          "range": "29 to 100"
        },
        {
          "track": "VENT_MAWP",
          "kind": "numeric",
          "unit": "mbar",
          "format": "float32",
          "rate": "2 s",
          "recordings": 6300,
          "grid": 1.0,
          "coarser_share": 0.192,
          "range": "-6 to 34"
        },
        {
          "track": "ETCO2",
          "kind": "numeric",
          "unit": "mmHg",
          "format": "float32",
          "rate": "2 s",
          "recordings": 6295,
          "grid": 1.0,
          "coarser_share": 0.216,
          "range": "0 to 86"
        },
        {
          "track": "INCO2",
          "kind": "numeric",
          "unit": "mmHg",
          "format": "float32",
          "rate": "2 s",
          "recordings": 6295,
          "grid": 1.0,
          "coarser_share": 0.216,
          "range": "0 to 46"
        },
        {
          "track": "FEO2",
          "kind": "numeric",
          "unit": "%",
          "format": "float32",
          "rate": "2 s",
          "recordings": 6278,
          "grid": 1.0,
          "coarser_share": 0.244,
          "range": "12 to 100"
        },
        {
          "track": "FIO2",
          "kind": "numeric",
          "unit": "%",
          "format": "float32",
          "rate": "2 s",
          "recordings": 6278,
          "grid": 1.0,
          "coarser_share": 0.244,
          "range": "16 to 100"
        },
        {
          "track": "RR_CO2",
          "kind": "numeric",
          "unit": "/min",
          "format": "float32",
          "rate": "2 s",
          "recordings": 6203,
          "grid": 1.0,
          "coarser_share": 0.213,
          "range": "0 to 53",
          "note": "corrupt values to 822"
        },
        {
          "track": "VENT_RR",
          "kind": "numeric",
          "unit": "/min",
          "format": "float32",
          "rate": "2 s",
          "recordings": 6051,
          "grid": 1.0,
          "coarser_share": 0.217,
          "range": "0 to 60"
        },
        {
          "track": "VENT_MV",
          "kind": "numeric",
          "unit": "L/min",
          "format": "float32",
          "rate": "2 s",
          "recordings": 6049,
          "grid": 0.1,
          "coarser_share": 0.205,
          "range": "0 to 20.1"
        },
        {
          "track": "ST_II",
          "kind": "numeric",
          "unit": "mm",
          "format": "float32",
          "rate": "0.075 s",
          "recordings": 6008,
          "grid": 0.1,
          "coarser_share": 0.281,
          "range": "-9.6 to 8.6"
        },
        {
          "track": "VENT_TV",
          "kind": "numeric",
          "unit": "mL",
          "format": "float32",
          "rate": "2 s",
          "recordings": 5985,
          "grid": 1.0,
          "coarser_share": 0.2,
          "range": "7 to 2450"
        },
        {
          "track": "VENT_PIP",
          "kind": "numeric",
          "unit": "mbar",
          "format": "float32",
          "rate": "2 s",
          "recordings": 5984,
          "grid": 1.0,
          "coarser_share": 0.192,
          "range": "-2 to 58"
        },
        {
          "track": "VENT_PPLAT",
          "kind": "numeric",
          "unit": "mbar",
          "format": "float32",
          "rate": "2 s",
          "recordings": 5961,
          "grid": 1.0,
          "coarser_share": 0.199,
          "range": "0 to 50"
        },
        {
          "track": "VENT_INSP_TM",
          "kind": "numeric",
          "unit": "sec",
          "format": "float32",
          "rate": "2 s",
          "recordings": 5940,
          "grid": 0.1,
          "coarser_share": 0.215,
          "range": "0.6 to 3.5"
        },
        {
          "track": "BT",
          "kind": "numeric",
          "unit": "C",
          "format": "float32",
          "rate": "2 s",
          "recordings": 5935,
          "grid": 0.1,
          "coarser_share": 0.195,
          "range": "1.1 to 39.7"
        },
        {
          "track": "NIBP_MBP",
          "kind": "numeric",
          "unit": "mmHg",
          "format": "float32",
          "rate": "2 s",
          "recordings": 5723,
          "grid": 1.0,
          "coarser_share": 0.2,
          "range": "0 to 222"
        },
        {
          "track": "NIBP_DBP",
          "kind": "numeric",
          "unit": "mmHg",
          "format": "float32",
          "rate": "2 s",
          "recordings": 5708,
          "grid": 1.0,
          "coarser_share": 0.201,
          "range": "0 to 197"
        },
        {
          "track": "NIBP_SBP",
          "kind": "numeric",
          "unit": "mmHg",
          "format": "float32",
          "rate": "2 s",
          "recordings": 5708,
          "grid": 1.0,
          "coarser_share": 0.2,
          "range": "0 to 274"
        },
        {
          "track": "VENT_SET_TV",
          "kind": "numeric",
          "unit": "mL",
          "format": "float32",
          "rate": "2 s",
          "recordings": 4261,
          "grid": 10.0,
          "coarser_share": 0.231,
          "range": "30 to 850"
        },
        {
          "track": "ART_DBP",
          "kind": "numeric",
          "unit": "mmHg",
          "format": "float32",
          "rate": "2 s",
          "recordings": 3535,
          "grid": 1.0,
          "coarser_share": 0.202,
          "range": "-96 to 348"
        },
        {
          "track": "ART_SBP",
          "kind": "numeric",
          "unit": "mmHg",
          "format": "float32",
          "rate": "2 s",
          "recordings": 3533,
          "grid": 1.0,
          "coarser_share": 0.203,
          "range": "-88 to 347"
        },
        {
          "track": "ART_MBP",
          "kind": "numeric",
          "unit": "mmHg",
          "format": "float32",
          "rate": "2 s",
          "recordings": 3532,
          "grid": 1.0,
          "coarser_share": 0.203,
          "range": "-84 to 349"
        },
        {
          "track": "ST_V5",
          "kind": "numeric",
          "unit": "mm",
          "format": "float32",
          "rate": "0.987 s",
          "recordings": 3353,
          "grid": 0.1,
          "coarser_share": 0.294,
          "range": "-10.9 to 10.7"
        },
        {
          "track": "ST_I",
          "kind": "numeric",
          "unit": "mm",
          "format": "float32",
          "rate": "0.724 s",
          "recordings": 3062,
          "grid": 0.1,
          "coarser_share": 0.48,
          "range": "-10.6 to 12.4"
        },
        {
          "track": "GAS2_EXPIRED",
          "kind": "numeric",
          "unit": "%",
          "format": "float32",
          "rate": "2 s",
          "recordings": 3053,
          "grid": 0.01,
          "coarser_share": 0.942,
          "range": "0 to 10.9"
        },
        {
          "track": "GAS2_INSPIRED",
          "kind": "numeric",
          "unit": "%",
          "format": "float32",
          "rate": "2 s",
          "recordings": 3053,
          "grid": 0.01,
          "coarser_share": 0.923,
          "range": "0 to 12.5"
        },
        {
          "track": "ST_III",
          "kind": "numeric",
          "unit": "mm",
          "format": "float32",
          "rate": "0.723 s",
          "recordings": 3051,
          "grid": 0.1,
          "coarser_share": 0.326,
          "range": "-11.5 to 9.1"
        },
        {
          "track": "ST_AVF",
          "kind": "numeric",
          "unit": "mm",
          "format": "float32",
          "rate": "0.893 s",
          "recordings": 3024,
          "grid": 0.1,
          "coarser_share": 0.266,
          "range": "-7.2 to 6"
        },
        {
          "track": "ST_AVL",
          "kind": "numeric",
          "unit": "mm",
          "format": "float32",
          "rate": "0.991 s",
          "recordings": 3024,
          "grid": 0.1,
          "coarser_share": 0.3,
          "range": "-6 to 6.3"
        },
        {
          "track": "ST_AVR",
          "kind": "numeric",
          "unit": "mm",
          "format": "float32",
          "rate": "0.991 s",
          "recordings": 3024,
          "grid": 0.1,
          "coarser_share": 0.3,
          "range": "-6 to 6.3"
        },
        {
          "track": "VENT_SET_PCP",
          "kind": "numeric",
          "unit": "cmH2O",
          "format": "float32",
          "rate": "2 s",
          "recordings": 2754,
          "grid": 1.0,
          "coarser_share": 0.85,
          "range": "12 to 61"
        },
        {
          "track": "VENT_SET_FIO2",
          "kind": "numeric",
          "unit": "%",
          "format": "float32",
          "rate": "2 s",
          "recordings": 2255,
          "grid": 10.0,
          "coarser_share": 0.049,
          "range": "21 to 100"
        },
        {
          "track": "CVP",
          "kind": "numeric",
          "unit": "mmHg",
          "format": "float32",
          "rate": "2 s",
          "recordings": 1462,
          "grid": 1.0,
          "coarser_share": 0.197,
          "range": "-78 to 347"
        },
        {
          "track": "RR",
          "kind": "numeric",
          "unit": "/min",
          "format": "float32",
          "rate": "2 s",
          "recordings": 1294,
          "grid": 1.0,
          "coarser_share": 0.218,
          "range": "0 to 200"
        },
        {
          "track": "VENT_MEAS_PEEP",
          "kind": "numeric",
          "unit": "hPa",
          "format": "float32",
          "rate": "2 s",
          "recordings": 173,
          "grid": 1.0,
          "coarser_share": 0.927,
          "range": "-3 to 33"
        },
        {
          "track": "FEM_DBP",
          "kind": "numeric",
          "unit": "mmHg",
          "format": "float32",
          "rate": "2 s",
          "recordings": 106,
          "grid": 1.0,
          "coarser_share": 0.202,
          "range": "-96 to 349"
        },
        {
          "track": "FEM_MBP",
          "kind": "numeric",
          "unit": "mmHg",
          "format": "float32",
          "rate": "2 s",
          "recordings": 106,
          "grid": 1.0,
          "coarser_share": 0.199,
          "range": "-70 to 348"
        },
        {
          "track": "FEM_SBP",
          "kind": "numeric",
          "unit": "mmHg",
          "format": "float32",
          "rate": "2 s",
          "recordings": 106,
          "grid": 1.0,
          "coarser_share": 0.2,
          "range": "-89 to 348"
        },
        {
          "track": "PA_DBP",
          "kind": "numeric",
          "unit": "mmHg",
          "format": "float32",
          "rate": "2 s",
          "recordings": 81,
          "grid": 1.0,
          "coarser_share": 0.2,
          "range": "-90 to 344"
        },
        {
          "track": "PA_MBP",
          "kind": "numeric",
          "unit": "mmHg",
          "format": "float32",
          "rate": "2 s",
          "recordings": 81,
          "grid": 1.0,
          "coarser_share": 0.193,
          "range": "-75 to 344"
        },
        {
          "track": "PA_SBP",
          "kind": "numeric",
          "unit": "mmHg",
          "format": "float32",
          "rate": "2 s",
          "recordings": 81,
          "grid": 1.0,
          "coarser_share": 0.199,
          "range": "-75 to 348"
        },
        {
          "track": "VENT_COMPL",
          "kind": "numeric",
          "unit": "mL/cmH2O",
          "format": "float32",
          "rate": "2 s",
          "recordings": 1,
          "grid": 1.0,
          "coarser_share": 0.0,
          "range": "14 to 14"
        }
      ],
      "name": "Solar8000 monitor",
      "summary": "GE Solar 8000M patient monitor: numerics (HR, pressures, SpO2, NIBP) and the analogue outputs SNUADC samples.",
      "type": "monitor",
      "clocks": [
        "solar8000-clock"
      ],
      "tags": [
        "stub"
      ],
      "docs": [
        {
          "title": "VitalDB connection guide: GE Solar 8000m / 8000i",
          "url": "https://vitaldb.net/docs/?documentId=VitalRecorder%2Fconnection-guide%2Fdevices%2Fpatient_monitors%2Fge_solar8000.md"
        }
      ],
      "derived": {
        "variables": [
          "solar8000-art-numerics",
          "solar8000-hr",
          "solar8000-nibp",
          "solar8000-pleth-hr",
          "solar8000-spo2"
        ],
        "studies": [
          "arterial-line-accesses",
          "hr-delay-groups",
          "monitor-lines",
          "numeric-clock-check",
          "numeric-delay-variability",
          "numeric-derivation-pilot",
          "pat-offset-census",
          "ppg-art-reference",
          "ppg-frame-clock",
          "ppg-offset-reference",
          "ppg-sawtooth-pauses",
          "ppg-sawtooth",
          "recorder-pc-steps",
          "setup-identification",
          "solar-snuadc-clock",
          "spo2-latency",
          "track-value-resolution"
        ],
        "observations": [
          "cuff-occludes-line",
          "hr-two-delay-groups",
          "numeric-delays-per-case",
          "numerics-not-front-stamped",
          "oximeter-frame-clock-fingerprint",
          "pleth-30hz-frames",
          "ppg-hardware-delay",
          "ppg-sawtooth",
          "solar-relays-primus",
          "solar-st-duplicated"
        ]
      }
    },
    "vigilance": {
      "id": "vigilance",
      "tracks": [
        {
          "track": "BT_PA",
          "kind": "numeric",
          "unit": "C",
          "format": "float32",
          "rate": "2 s",
          "recordings": 63,
          "grid": 0.1,
          "coarser_share": 0.19,
          "range": "0 to 40.8"
        },
        {
          "track": "SQI",
          "kind": "numeric",
          "format": "float32",
          "rate": "2 s",
          "recordings": 62,
          "grid": 1.0,
          "coarser_share": 0.0,
          "range": "1 to 4"
        },
        {
          "track": "SVO2",
          "kind": "numeric",
          "unit": "%",
          "format": "float32",
          "rate": "2 s",
          "recordings": 62,
          "grid": 1.0,
          "coarser_share": 0.201,
          "range": "0 to 97"
        },
        {
          "track": "CI",
          "kind": "numeric",
          "unit": "L/min/m2",
          "format": "float32",
          "rate": "2 s",
          "recordings": 58,
          "grid": 0.1,
          "coarser_share": 0.196,
          "range": "1 to 9.3"
        },
        {
          "track": "CO",
          "kind": "numeric",
          "unit": "L/min",
          "format": "float32",
          "rate": "2 s",
          "recordings": 58,
          "grid": 0.1,
          "coarser_share": 0.199,
          "range": "1.6 to 17.4"
        },
        {
          "track": "SNR",
          "kind": "numeric",
          "unit": "dB",
          "format": "float32",
          "rate": "2 s",
          "recordings": 58,
          "grid": 0.1,
          "coarser_share": 0.265,
          "range": "-10 to 31.7"
        },
        {
          "track": "HR_AVG",
          "kind": "numeric",
          "unit": "/min",
          "format": "float32",
          "rate": "2 s",
          "recordings": 52,
          "grid": 1.0,
          "coarser_share": 0.207,
          "range": "22 to 223"
        },
        {
          "track": "EDV",
          "kind": "numeric",
          "unit": "ml",
          "format": "float32",
          "rate": "2 s",
          "recordings": 48,
          "grid": 1.0,
          "coarser_share": 0.197,
          "range": "0 to 647"
        },
        {
          "track": "EDVI",
          "kind": "numeric",
          "unit": "ml/m2",
          "format": "float32",
          "rate": "2 s",
          "recordings": 48,
          "grid": 1.0,
          "coarser_share": 0.201,
          "range": "0 to 389"
        },
        {
          "track": "ESV",
          "kind": "numeric",
          "unit": "ml",
          "format": "float32",
          "rate": "2 s",
          "recordings": 48,
          "grid": 1.0,
          "coarser_share": 0.199,
          "range": "1 to 563"
        },
        {
          "track": "ESVI",
          "kind": "numeric",
          "unit": "ml/m2",
          "format": "float32",
          "rate": "2 s",
          "recordings": 48,
          "grid": 1.0,
          "coarser_share": 0.203,
          "range": "0 to 339"
        },
        {
          "track": "SV",
          "kind": "numeric",
          "unit": "ml/beat",
          "format": "float32",
          "rate": "2 s",
          "recordings": 48,
          "grid": 1.0,
          "coarser_share": 0.202,
          "range": "0 to 195"
        },
        {
          "track": "SVI",
          "kind": "numeric",
          "unit": "ml/beat/m2",
          "format": "float32",
          "rate": "2 s",
          "recordings": 48,
          "grid": 1.0,
          "coarser_share": 0.192,
          "range": "0 to 113"
        },
        {
          "track": "RVEF",
          "kind": "numeric",
          "unit": "%",
          "format": "float32",
          "rate": "2 s",
          "recordings": 47,
          "grid": 1.0,
          "coarser_share": 0.202,
          "range": "0 to 60"
        }
      ],
      "name": "Vigilance",
      "summary": "Edwards Vigilance: continuous cardiac output.",
      "type": "monitor",
      "tags": [
        "stub"
      ],
      "docs": [
        {
          "title": "VitalDB connection guide: Edwards Vigilance",
          "url": "https://vitaldb.net/docs/?documentId=VitalRecorder%2Fconnection-guide%2Fdevices%2Fhemodynamic_monitors%2Fedwards_vigilance.md"
        },
        {
          "title": "VitalDB connection guide: Edwards Vigilance II",
          "url": "https://vitaldb.net/docs/?documentId=VitalRecorder%2Fconnection-guide%2Fdevices%2Fhemodynamic_monitors%2Fedwards_vigilance2.md"
        }
      ],
      "derived": {
        "variables": [
          "vigilance-co"
        ],
        "studies": [
          "rare-device-sync",
          "track-value-resolution"
        ]
      }
    },
    "vigileo": {
      "id": "vigileo",
      "tracks": [
        {
          "track": "CI",
          "kind": "numeric",
          "unit": "L/min/m2",
          "format": "float32",
          "rate": "2 s",
          "recordings": 348,
          "grid": 0.1,
          "coarser_share": 0.2,
          "range": "0.3 to 8.8"
        },
        {
          "track": "CO",
          "kind": "numeric",
          "unit": "L/min",
          "format": "float32",
          "rate": "2 s",
          "recordings": 348,
          "grid": 0.1,
          "coarser_share": 0.202,
          "range": "1 to 15.4"
        },
        {
          "track": "SV",
          "kind": "numeric",
          "unit": "ml/beat",
          "format": "float32",
          "rate": "2 s",
          "recordings": 348,
          "grid": 1.0,
          "coarser_share": 0.201,
          "range": "5 to 176"
        },
        {
          "track": "SVI",
          "kind": "numeric",
          "unit": "ml/beat/m2",
          "format": "float32",
          "rate": "2 s",
          "recordings": 348,
          "grid": 1.0,
          "coarser_share": 0.199,
          "range": "4 to 95"
        },
        {
          "track": "SVV",
          "kind": "numeric",
          "unit": "%",
          "format": "float32",
          "rate": "2 s",
          "recordings": 348,
          "grid": 1.0,
          "coarser_share": 0.204,
          "range": "0 to 76"
        }
      ],
      "name": "Vigileo",
      "summary": "Edwards Vigileo: SV, CO and SVV.",
      "type": "monitor",
      "tags": [
        "stub"
      ],
      "docs": [
        {
          "title": "VitalDB connection guide: Edwards Vigileo",
          "url": "https://vitaldb.net/docs/?documentId=VitalRecorder%2Fconnection-guide%2Fdevices%2Fhemodynamic_monitors%2Fedwards_vigileo.md"
        }
      ],
      "derived": {
        "variables": [
          "vigileo-sv"
        ],
        "studies": [
          "rare-device-sync",
          "track-value-resolution"
        ]
      }
    },
    "vital-recorder": {
      "id": "vital-recorder",
      "name": "Vital Recorder",
      "summary": "The recording software that collects every device's data and writes the .vital files.",
      "type": "software",
      "clocks": [
        "recorder-pc"
      ],
      "description": "Vital Recorder runs on a PC in each operating room. It stamps numeric records on arrival and stamps waveform\nrecords from its model of each device's timing, re-synchronising that model when it drifts too far.\n",
      "references": [
        "lee2022vitaldb",
        "lee2018vitalrecorder"
      ],
      "docs": [
        {
          "title": "VitalRecorder user manual",
          "url": "https://vitaldb.net/docs/?documentId=VitalRecorder%2FUser_Manual.md"
        },
        {
          "title": "VitalRecorder configuration guide",
          "url": "https://vitaldb.net/docs/?documentId=VitalRecorder%2FConfiguration_Guide.md"
        },
        {
          "title": "Vital file format",
          "url": "https://vitaldb.net/docs/?documentId=VitalRecorder%2FVital_File_Format.md"
        }
      ],
      "derived": {
        "studies": [
          "case-clock",
          "clock-correction-scan",
          "continuity-gaps-segments",
          "drift-units",
          "emr-rounding",
          "hr-delay-groups",
          "loop-closure",
          "paced-ecg",
          "record-messages-and-steps",
          "recorder-pc-steps",
          "reset-cluster-order",
          "setup-identification",
          "snuadc-firmware",
          "snuadc-stamp-sawtooth",
          "solar-snuadc-clock",
          "stamp-arithmetic",
          "step-classification",
          "time-zeros",
          "vitaldb-loader"
        ],
        "observations": [
          "device-drift-by-setup",
          "duplicated-records",
          "event-track-generated",
          "hr-two-delay-groups",
          "interleaved-records",
          "loaded-subset-time-zero",
          "recording-pauses",
          "release-time-shift",
          "reset-records-out-of-order",
          "snuadc-no-timestamps",
          "snuadc-overlapping-records",
          "snuadc-stamp-arithmetic"
        ]
      }
    }
  },
  "variables": {
    "adm": {
      "id": "adm",
      "name": "Admission time from casestart",
      "summary": "Admission time from casestart (clinical table).",
      "type": "time",
      "source_name": "adm",
      "group": "Clinical record/Times and durations",
      "source": "clinical_data.csv",
      "unit": "sec",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "86400 sec, floor",
        "step": 86400,
        "anchor": "floor",
        "anchor_basis": "tested",
        "anchor_note": "Discharge minus admission is a whole number of days in every case, so each value marks a midnight.",
        "spread": "vitaldb-resolution/spread.json#adm"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Discharge minus admission is a whole number of days in every case; value marks midnight.\n\nHow its time was recorded is described with [[variable:admission-discharge]].",
      "coupling": {
        "status": "coupled",
        "note": "The value is itself a time: its value side and its time side are the same uncertainty."
      },
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/adm.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,388 records, median -2.011e+05 sec; 64 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of adm."
        }
      ],
      "derived": {}
    },
    "admission-discharge": {
      "id": "admission-discharge",
      "name": "Admission and discharge",
      "summary": "Hospital admission and discharge, in days.",
      "group": "Clinical record/Times and durations",
      "type": "event",
      "source_name": "adm, dis",
      "device": "clinical-records",
      "stamp_clock": "hospital-records",
      "timing": [
        {
          "id": "admdis",
          "name": "as recorded",
          "default": true,
          "delay_s": [
            3600.0,
            86400.0
          ],
          "interval_s": [
            86400.0,
            86400.0
          ],
          "domain": "records",
          "status": "estimated",
          "evidence": "whole days"
        }
      ],
      "tags": [
        "stub"
      ],
      "derived": {
        "studies": [
          "case-clock"
        ]
      }
    },
    "age": {
      "id": "age",
      "name": "Age",
      "summary": "Age (clinical table).",
      "type": "clinical",
      "source_name": "age",
      "group": "Clinical record/Patient",
      "source": "clinical_data.csv",
      "unit": "years",
      "value": {
        "kind": "age: completed years, top-coded and infant rules",
        "resolution": "completed years; tenths for infants; top-coded",
        "step": 1,
        "anchor": "floor",
        "anchor_basis": "assumption",
        "anchor_note": "The release says only \"years\"; infants in tenths and the >89 cap point to an exact age cut down to completed years.",
        "spread": "vitaldb-resolution/spread.json#age"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Completed years; 19.2% end in 0 or 5 (20% expected), so no heaping. 8 values top-coded \">89\"; 10 infants recorded in tenths of a year.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/age.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,380 records, median 59 years; 53 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of age."
        }
      ],
      "derived": {}
    },
    "airway": {
      "id": "airway",
      "name": "Airway route",
      "summary": "Airway route (clinical table).",
      "type": "clinical",
      "source_name": "airway",
      "group": "Clinical record/Airway and lines",
      "source": "clinical_data.csv",
      "value": {
        "kind": "not_applicable: categorical or identifier"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Discrete category or device size; not a resolution question.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/airway.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 5,973 records in 3 categories.",
          "alt": "Histogram of the recorded values of airway."
        }
      ],
      "derived": {}
    },
    "alb": {
      "id": "alb",
      "name": "Albumin",
      "summary": "Albumin (lab table).",
      "type": "lab",
      "source_name": "alb",
      "group": "Laboratory/Chemistry",
      "source": "lab_data.csv",
      "unit": "g/dL",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.1 g/dL, nearest",
        "step": 0.1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#alb"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; last digit flat.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/alb.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 36,629 records, median 3.3 g/dL; 305 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of alb."
        }
      ],
      "derived": {}
    },
    "aline1": {
      "id": "aline1",
      "name": "Site of arterial line (1)",
      "summary": "Site of arterial line (1) (clinical table).",
      "type": "clinical",
      "source_name": "aline1",
      "group": "Clinical record/Airway and lines",
      "source": "clinical_data.csv",
      "value": {
        "kind": "not_applicable: categorical or identifier"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Discrete category or device size; not a resolution question.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/aline1.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 3,463 records in 9 categories.",
          "alt": "Histogram of the recorded values of aline1."
        }
      ],
      "derived": {}
    },
    "aline2": {
      "id": "aline2",
      "name": "Site of arterial line (2)",
      "summary": "Site of arterial line (2) (clinical table).",
      "type": "clinical",
      "source_name": "aline2",
      "group": "Clinical record/Airway and lines",
      "source": "clinical_data.csv",
      "value": {
        "kind": "not_applicable: categorical or identifier"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Discrete category or device size; not a resolution question.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/aline2.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 105 records in 4 categories.",
          "alt": "Histogram of the recorded values of aline2."
        }
      ],
      "derived": {}
    },
    "alt": {
      "id": "alt",
      "name": "Alanine transferase",
      "summary": "Alanine transferase (lab table).",
      "type": "lab",
      "source_name": "alt",
      "group": "Laboratory/Chemistry",
      "source": "lab_data.csv",
      "unit": "IU/L",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "1 IU/L, nearest",
        "step": 1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#alt"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; no heaping in the whole-number screen.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/alt.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 35,918 records, median 24 IU/L; 342 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of alt."
        }
      ],
      "derived": {}
    },
    "ammo": {
      "id": "ammo",
      "name": "Ammonia",
      "summary": "Ammonia (lab table).",
      "type": "lab",
      "source_name": "ammo",
      "group": "Laboratory/Chemistry",
      "source": "lab_data.csv",
      "unit": "mcg/dL",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "1 mcg/dL, nearest",
        "step": 1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#ammo"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; no heaping in the whole-number screen.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/ammo.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 864 records, median 86.5 mcg/dL; 10 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of ammo."
        }
      ],
      "derived": {}
    },
    "ane-duration": {
      "id": "ane-duration",
      "name": "Anaesthesia duration",
      "summary": "Anaesthesia duration (derived table).",
      "type": "clinical",
      "source_name": "ane_duration",
      "group": "Clinical record/Times and durations",
      "source": "derived",
      "unit": "min",
      "value": {
        "kind": "derived: computed from other variables' posteriors",
        "resolution": "derived from anestart, aneend",
        "inputs": [
          "anestart",
          "aneend"
        ],
        "formula": "(aneend - anestart) / 60",
        "combination": "difference of the two event posteriors, sampled independently given the device references",
        "spread": "vitaldb-resolution/spread.json#ane_duration"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "derived": {
        "examples": [
          "oliguria-labels",
          "regression-with-rounding",
          "urine-output-per-kg-per-hour"
        ]
      }
    },
    "ane-type": {
      "id": "ane-type",
      "name": "Anesthesia type",
      "summary": "Anesthesia type (clinical table).",
      "type": "clinical",
      "source_name": "ane_type",
      "group": "Clinical record/Operation and anaesthesia",
      "source": "clinical_data.csv",
      "value": {
        "kind": "not_applicable: categorical or identifier"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Discrete category or device size; not a resolution question.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/ane-type.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,388 records in 3 categories.",
          "alt": "Histogram of the recorded values of ane_type."
        }
      ],
      "derived": {}
    },
    "aneend": {
      "id": "aneend",
      "name": "Anesthesia end time",
      "summary": "Anesthesia end time; from casestart (clinical table).",
      "type": "time",
      "source_name": "aneend",
      "group": "Clinical record/Times and durations",
      "source": "clinical_data.csv",
      "unit": "sec",
      "value": {
        "kind": "timestamp_model: fitted reference-offset model; posterior over the true event time",
        "resolution": "heaped: a mixture of rounding modes (see the value model)",
        "spread": "vitaldb-resolution/spread.json#aneend",
        "model": "vitaldb-resolution/vitaldb_resolution/data/models/aneend.model.json",
        "status": "measured",
        "studies": [
          "value-model-aneend"
        ]
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Charted clock minute modelled as an offset from the recording end (device time). See the Timestamps section.\n\nHow its time was recorded is described with [[variable:emr-events]].",
      "stamp_clock": "hospital-records",
      "coupling": {
        "status": "coupled",
        "note": "The value is itself a time: its value side and its time side are the same uncertainty."
      },
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/aneend.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,388 records, median 9770 sec; 64 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of aneend."
        }
      ],
      "derived": {
        "studies": [
          "value-model-aneend"
        ]
      }
    },
    "anestart": {
      "id": "anestart",
      "name": "Anesthesia start time",
      "summary": "Anesthesia start time; from casestart (clinical table).",
      "type": "time",
      "source_name": "anestart",
      "group": "Clinical record/Times and durations",
      "source": "clinical_data.csv",
      "unit": "sec",
      "value": {
        "kind": "timestamp_model: fitted reference-offset model; posterior over the true event time",
        "resolution": "heaped: a mixture of rounding modes (see the value model)",
        "spread": "vitaldb-resolution/spread.json#anestart",
        "model": "vitaldb-resolution/vitaldb_resolution/data/models/anestart.model.json",
        "status": "measured",
        "studies": [
          "value-model-anestart"
        ]
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Charted clock minute modelled as an offset from the recording start (device time). See the Timestamps section.\n\nHow its time was recorded is described with [[variable:emr-events]].",
      "stamp_clock": "hospital-records",
      "coupling": {
        "status": "coupled",
        "note": "The value is itself a time: its value side and its time side are the same uncertainty."
      },
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/anestart.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,388 records, median -583 sec; 64 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of anestart."
        }
      ],
      "derived": {
        "studies": [
          "value-model-anestart"
        ]
      }
    },
    "approach": {
      "id": "approach",
      "name": "Surgical approach",
      "summary": "Surgical approach (clinical table).",
      "type": "clinical",
      "source_name": "approach",
      "group": "Clinical record/Operation and anaesthesia",
      "source": "clinical_data.csv",
      "value": {
        "kind": "not_applicable: categorical or identifier"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Discrete category or device size; not a resolution question.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/approach.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,388 records in 3 categories.",
          "alt": "Histogram of the recorded values of approach."
        }
      ],
      "derived": {}
    },
    "aptt": {
      "id": "aptt",
      "name": "Activated partial thromboplastin time",
      "summary": "Activated partial thromboplastin time (lab table).",
      "type": "lab",
      "source_name": "aptt",
      "group": "Laboratory/Coagulation",
      "source": "lab_data.csv",
      "unit": "sec",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.1 sec, nearest",
        "step": 0.1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#aptt"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; last digit flat.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/aptt.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 20,542 records, median 32.3 sec; 101 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of aptt."
        }
      ],
      "derived": {
        "observations": [
          "lab-reporting-limits"
        ]
      }
    },
    "asa": {
      "id": "asa",
      "name": "ASA Physical status classification",
      "summary": "ASA Physical status classification (clinical table).",
      "type": "clinical",
      "source_name": "asa",
      "group": "Clinical record/Patient",
      "source": "clinical_data.csv",
      "value": {
        "kind": "not_applicable: categorical or identifier"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Discrete category or device size; not a resolution question.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/asa.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,255 records in 5 categories.",
          "alt": "Histogram of the recorded values of asa."
        }
      ],
      "derived": {}
    },
    "ast": {
      "id": "ast",
      "name": "Asparate transferase",
      "summary": "Asparate transferase (lab table).",
      "type": "lab",
      "source_name": "ast",
      "group": "Laboratory/Chemistry",
      "source": "lab_data.csv",
      "unit": "IU/L",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "1 IU/L, nearest",
        "step": 1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#ast"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; no heaping in the whole-number screen.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/ast.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 35,919 records, median 26 IU/L; 349 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of ast."
        }
      ],
      "derived": {}
    },
    "be": {
      "id": "be",
      "name": "Base excess",
      "summary": "Base excess (lab table).",
      "type": "lab",
      "source_name": "be",
      "group": "Laboratory/Blood gas",
      "source": "lab_data.csv",
      "unit": "mmol/L",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.1 mmol/L, nearest",
        "step": 0.1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#be"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; last digit flat.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/be.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 3,911 records, median 3 mmol/L; 20 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of be."
        }
      ],
      "derived": {}
    },
    "bis-eeg": {
      "id": "bis-eeg",
      "name": "BIS EEG",
      "summary": "The raw EEG from the BIS monitor at 128 Hz.",
      "type": "waveform",
      "source_name": "BIS/EEG1_WAV",
      "device": "bis",
      "stamp_clock": "recorder-pc",
      "sample_clock": "bis-clock",
      "rate_hz": 128,
      "unit": "uV",
      "timing": [
        {
          "id": "bis-sync",
          "name": "after sync to the ECG",
          "default": true,
          "delay_s": [
            0.017,
            0.042
          ],
          "interval_s": [
            0.0078,
            0.0078
          ],
          "domain": "reference",
          "status": "measured",
          "evidence": "17-42 ms (recording's own constant to the mode's; whole release, 2,710 recordings); loop closes in drift and steps; latency -0.80 s (SD 25 ms) in link K",
          "corrected_delay_s": [
            0.53,
            1.05
          ],
          "correction": "stamp model and ECG-EEG burst constant",
          "corrects": [
            "bis-raw"
          ]
        },
        {
          "id": "bis-raw",
          "name": "raw stamps",
          "delay_s": [
            0.53,
            1.05
          ],
          "interval_s": [
            0.0078,
            0.0078
          ],
          "domain": "bis",
          "status": "measured",
          "evidence": "offset p5-p95 -1053..-533 ms on the recorded stamps (191-case pilot)"
        }
      ],
      "tags": [
        "stub"
      ],
      "chain": [
        {
          "node": "BIS clock",
          "clock": "bis-clock",
          "kind": "clock",
          "effect": "The BIS monitor stamps its EEG on its own clock; for the same event its stamps sit 0.53–1.05 s off the ECG's. It drifts +25 ppm against the PC and is pulled forward on a fixed 8,192 s timer (median +0.17 s).",
          "offset_s": [
            0.53,
            1.05
          ],
          "drift": "+25 ppm against the PC; corrected every 8,192 s",
          "status": "measured",
          "studies": [
            "snuadc-stamp-sawtooth"
          ]
        },
        {
          "node": "Synchronised to the ECG",
          "kind": "correction",
          "effect": "The stamp model removes the drift and corrections; one constant per SNUADC mode, from bursts seen in both the ECG and the EEG, removes the offset. With the recording's own bursts the error is about 17 ms, with the mode's constant about 42 ms (whole release).",
          "offset_s": [
            0.53,
            1.05
          ],
          "residual_s": [
            0.017,
            0.042
          ],
          "status": "measured",
          "studies": [
            "snuadc-stamp-sawtooth"
          ]
        },
        {
          "node": "Stored sample time",
          "kind": "stamping",
          "effect": "Samples at 128 Hz: one sample is 7.8 ms.",
          "offset_s": [
            0.0078,
            0.0078
          ],
          "status": "measured"
        }
      ],
      "derived": {
        "parameters": [
          "stamp-model-error"
        ],
        "studies": [
          "bis-cardiac-artefact",
          "bis-index-delay",
          "clock-correction-scan",
          "continuity-gaps-segments",
          "correction-periods",
          "device-correction-schedules",
          "loop-closure",
          "openeeg-bis-mimic",
          "record-messages-and-steps",
          "shared-bursts",
          "snuadc-stamp-sawtooth",
          "step-classification",
          "track-value-resolution"
        ],
        "observations": [
          "bis-eeg-cardiac-artefact",
          "bis-eeg-no-heartbeat",
          "bis-index-slow",
          "duplicated-records",
          "eeg-ecg-offset-by-mode",
          "fiducial-quantisation",
          "interleaved-records"
        ]
      }
    },
    "bis-index": {
      "id": "bis-index",
      "name": "BIS index",
      "summary": "The BIS depth-of-anaesthesia index, computed from a window of EEG.",
      "type": "numeric",
      "source_name": "BIS/BIS",
      "device": "bis",
      "stamp_clock": "bis-clock",
      "timing": [
        {
          "id": "bis-num",
          "name": "as stamped",
          "default": true,
          "delay_s": [
            3.0,
            4.0
          ],
          "interval_s": [
            16.0,
            61.0
          ],
          "domain": "bis",
          "status": "measured",
          "evidence": "3-4 s after its EEG window ends; window 16 s (48%) or 32-61 s (40%); half the weight older than 30 s"
        }
      ],
      "tags": [
        "stub"
      ],
      "window": {
        "name": "Trailing average",
        "parameter": "bis-index-window",
        "description": "Set per case on the monitor; not recorded."
      },
      "delays": [
        {
          "name": "From the EEG window end",
          "parameter": "bis-index-delay",
          "description": "The same in every smoothing group."
        }
      ],
      "derived": {
        "parameters": [
          "bis-index-delay",
          "bis-index-window"
        ],
        "studies": [
          "bis-index-delay",
          "drug-effect-latency",
          "emr-rounding",
          "openeeg-bis-mimic",
          "track-value-resolution"
        ],
        "observations": [
          "bis-index-slow",
          "numerics-not-front-stamped"
        ],
        "examples": [
          "induction-hypotension"
        ]
      }
    },
    "bmi": {
      "id": "bmi",
      "name": "Body mass index",
      "summary": "Body mass index (clinical table).",
      "type": "clinical",
      "source_name": "bmi",
      "group": "Clinical record/Patient",
      "source": "clinical_data.csv",
      "unit": "kg/m2",
      "value": {
        "kind": "derived: computed from other variables' posteriors",
        "resolution": "derived from height, weight",
        "inputs": [
          "height",
          "weight"
        ],
        "spread": "vitaldb-resolution/spread.json#bmi",
        "model": "vitaldb-resolution/vitaldb_resolution/data/models/bmi.model.json",
        "status": "measured",
        "studies": [
          "value-model-bmi"
        ]
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/bmi.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,388 records, median 23.1 kg/m2; 61 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of bmi."
        }
      ],
      "derived": {
        "studies": [
          "value-model-bmi"
        ]
      }
    },
    "bun": {
      "id": "bun",
      "name": "Blood urea nitrogen",
      "summary": "Blood urea nitrogen (lab table).",
      "type": "lab",
      "source_name": "bun",
      "group": "Laboratory/Chemistry",
      "source": "lab_data.csv",
      "unit": "mg/dL",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "1 mg/dL, nearest",
        "step": 1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#bun"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; no heaping in the whole-number screen.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/bun.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 37,213 records, median 15 mg/dL; 235 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of bun."
        }
      ],
      "derived": {}
    },
    "cardioq-abp": {
      "id": "cardioq-abp",
      "name": "CardioQ pressure and flow",
      "summary": "Arterial pressure and aortic flow waveforms from the CardioQ oesophageal Doppler monitor.",
      "type": "waveform",
      "source_name": "CardioQ/ABP",
      "device": "cardioq",
      "stamp_clock": "recorder-pc",
      "sample_clock": "cardioq-clock",
      "rate_hz": 180,
      "timing": [
        {
          "id": "cardioq-sync",
          "name": "after beat matching",
          "default": true,
          "delay_s": [
            0.1,
            0.3
          ],
          "interval_s": [
            0.0056,
            0.0056
          ],
          "domain": "reference",
          "status": "measured",
          "evidence": "per stretch; matched-beat MAD 0.24 s, R-R correlation 0.96 (24 cases)",
          "corrected_delay_s": [
            0.4,
            4.4
          ],
          "correction": "beat matching to SNUADC ART",
          "corrects": [
            "cardioq-raw"
          ]
        },
        {
          "id": "cardioq-raw",
          "name": "raw stamps",
          "delay_s": [
            0.4,
            4.4
          ],
          "interval_s": [
            0.0056,
            0.0056
          ],
          "domain": "cardioq",
          "status": "measured",
          "evidence": "lag vs SNUADC ART p5-p95 0.4-4.4 s; re-anchors ~40 times per case"
        }
      ],
      "tags": [
        "stub"
      ],
      "derived": {
        "studies": [
          "loop-closure",
          "rare-device-sync"
        ]
      }
    },
    "case-end": {
      "id": "case-end",
      "name": "Case end",
      "summary": "The end of the recording in the clinical table.",
      "type": "event",
      "source_name": "caseend",
      "group": "Clinical record/Times and durations",
      "source": "clinical_data.csv",
      "unit": "sec",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "1 sec, floor",
        "step": 1,
        "anchor": "floor",
        "anchor_basis": "convention",
        "anchor_note": "Clock truncation to the whole second; at most 1 s, negligible.",
        "spread": "vitaldb-resolution/spread.json#caseend"
      },
      "tags": [
        "stub"
      ],
      "description": "Device clock, whole seconds.",
      "stamp_clock": "hospital-records",
      "coupling": {
        "status": "coupled",
        "note": "The value is itself a time: its value side and its time side are the same uncertainty."
      },
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/case-end.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,388 records, median 9924 sec; 64 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of caseend."
        }
      ],
      "device": "clinical-records",
      "timing": [
        {
          "id": "caseend",
          "name": "as recorded",
          "default": true,
          "delay_s": [
            0.01,
            0.5
          ],
          "interval_s": [
            1.0,
            1.0
          ],
          "domain": "records",
          "status": "measured",
          "evidence": "recording end rounded to 1 s (98.7% within 1 s)"
        }
      ],
      "derived": {
        "observations": [
          "event-track-generated"
        ]
      }
    },
    "casestart": {
      "id": "casestart",
      "name": "Recording Start Time",
      "summary": "Recording Start Time; Set to 0 for anonymization (clinical table).",
      "type": "time",
      "source_name": "casestart",
      "group": "Clinical record/Times and durations",
      "source": "clinical_data.csv",
      "unit": "sec",
      "value": {
        "kind": "exact: exact count; point mass",
        "resolution": "exact",
        "step": 1,
        "spread": "vitaldb-resolution/spread.json#casestart"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Recording start; set to 0 for anonymisation.",
      "stamp_clock": "recorder-pc",
      "coupling": {
        "status": "coupled",
        "note": "The value is itself a time: its value side and its time side are the same uncertainty."
      },
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/casestart.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,388 records, median 0 sec; all shown.",
          "alt": "Histogram of the recorded values of casestart."
        }
      ],
      "derived": {}
    },
    "ccr": {
      "id": "ccr",
      "name": "Creatinine clearance",
      "summary": "Creatinine clearance (lab table).",
      "type": "lab",
      "source_name": "ccr",
      "group": "Laboratory/Chemistry",
      "source": "lab_data.csv",
      "unit": "mL/min",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "1 mL/min, nearest",
        "step": 1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#ccr"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; no heaping in the whole-number screen.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/ccr.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 537 records, median 72 mL/min; 3 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of ccr."
        }
      ],
      "derived": {}
    },
    "cl": {
      "id": "cl",
      "name": "Chloride",
      "summary": "Chloride (lab table).",
      "type": "lab",
      "source_name": "cl",
      "group": "Laboratory/Electrolytes and glucose",
      "source": "lab_data.csv",
      "unit": "mmol/L",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "1 mmol/L, nearest",
        "step": 1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#cl"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; no heaping in the whole-number screen.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/cl.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 40,863 records, median 103 mmol/L; 336 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of cl."
        }
      ],
      "derived": {}
    },
    "cline1": {
      "id": "cline1",
      "name": "Site of central line (1)",
      "summary": "Site of central line (1) (clinical table).",
      "type": "clinical",
      "source_name": "cline1",
      "group": "Clinical record/Airway and lines",
      "source": "clinical_data.csv",
      "value": {
        "kind": "not_applicable: categorical or identifier"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Discrete category or device size; not a resolution question.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/cline1.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 1,541 records in 14 categories (12 most common shown).",
          "alt": "Histogram of the recorded values of cline1."
        }
      ],
      "derived": {}
    },
    "cline2": {
      "id": "cline2",
      "name": "Site of central line (2)",
      "summary": "Site of central line (2) (clinical table).",
      "type": "clinical",
      "source_name": "cline2",
      "group": "Clinical record/Airway and lines",
      "source": "clinical_data.csv",
      "value": {
        "kind": "not_applicable: categorical or identifier"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Discrete category or device size; not a resolution question.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/cline2.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 60 records in 10 categories.",
          "alt": "Histogram of the recorded values of cline2."
        }
      ],
      "derived": {}
    },
    "cormack": {
      "id": "cormack",
      "name": "Cormack's grade",
      "summary": "Cormack's grade (clinical table).",
      "type": "clinical",
      "source_name": "cormack",
      "group": "Clinical record/Airway and lines",
      "source": "clinical_data.csv",
      "value": {
        "kind": "not_applicable: categorical or identifier"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Discrete category or device size; not a resolution question.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/cormack.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 5,553 records in 5 categories.",
          "alt": "Histogram of the recorded values of cormack."
        }
      ],
      "derived": {}
    },
    "cr": {
      "id": "cr",
      "name": "Creatinine",
      "summary": "Creatinine (lab table).",
      "type": "lab",
      "source_name": "cr",
      "group": "Laboratory/Chemistry",
      "source": "lab_data.csv",
      "unit": "mg/dL",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.01 mg/dL, nearest",
        "step": 0.01,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#cr"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; last digit flat.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/cr.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 37,311 records, median 0.78 mg/dL; 369 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of cr."
        }
      ],
      "derived": {
        "examples": [
          "regression-with-rounding"
        ]
      }
    },
    "crp": {
      "id": "crp",
      "name": "C-reactive protein",
      "summary": "C-reactive protein (lab table).",
      "type": "lab",
      "source_name": "crp",
      "group": "Laboratory/Chemistry",
      "source": "lab_data.csv",
      "unit": "mg/dL",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.01 mg/dL, nearest",
        "step": 0.01,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#crp"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; last digit flat.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/crp.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 28,278 records, median 3.31 mg/dL; 142 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of crp."
        }
      ],
      "derived": {}
    },
    "death-inhosp": {
      "id": "death-inhosp",
      "name": "In-hospital Mortality",
      "summary": "In-hospital Mortality (clinical table).",
      "type": "clinical",
      "source_name": "death_inhosp",
      "group": "Clinical record/Outcome",
      "source": "clinical_data.csv",
      "value": {
        "kind": "not_applicable: categorical or identifier"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/death-inhosp.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,388 records in 2 categories.",
          "alt": "Histogram of the recorded values of death_inhosp."
        }
      ],
      "derived": {}
    },
    "department": {
      "id": "department",
      "name": "Surgical department",
      "summary": "Surgical department (clinical table).",
      "type": "clinical",
      "source_name": "department",
      "group": "Clinical record/Operation and anaesthesia",
      "source": "clinical_data.csv",
      "value": {
        "kind": "not_applicable: categorical or identifier"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Discrete category or device size; not a resolution question.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/department.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,388 records in 4 categories.",
          "alt": "Histogram of the recorded values of department."
        }
      ],
      "derived": {}
    },
    "dis": {
      "id": "dis",
      "name": "Discharge time from casestart",
      "summary": "Discharge time from casestart (clinical table).",
      "type": "time",
      "source_name": "dis",
      "group": "Clinical record/Times and durations",
      "source": "clinical_data.csv",
      "unit": "sec",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "86400 sec, floor",
        "step": 86400,
        "anchor": "floor",
        "anchor_basis": "tested",
        "anchor_note": "Discharge minus admission is a whole number of days in every case, so each value marks a midnight.",
        "spread": "vitaldb-resolution/spread.json#dis"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Discharge minus admission is a whole number of days in every case; value marks midnight.\n\nHow its time was recorded is described with [[variable:admission-discharge]].",
      "coupling": {
        "status": "coupled",
        "note": "The value is itself a time: its value side and its time side are the same uncertainty."
      },
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/dis.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,388 records, median 3.932e+05 sec; 64 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of dis."
        }
      ],
      "derived": {}
    },
    "dltubesize": {
      "id": "dltubesize",
      "name": "Double lumen tube size",
      "summary": "Double lumen tube size (clinical table).",
      "type": "clinical",
      "source_name": "dltubesize",
      "group": "Clinical record/Airway and lines",
      "source": "clinical_data.csv",
      "unit": "Fr",
      "value": {
        "kind": "not_applicable: categorical or identifier"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Discrete category or device size; not a resolution question.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/dltubesize.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 930 records in 9 categories.",
          "alt": "Histogram of the recorded values of dltubesize."
        }
      ],
      "derived": {}
    },
    "dx": {
      "id": "dx",
      "name": "Diagnosis",
      "summary": "Diagnosis (clinical table).",
      "type": "clinical",
      "source_name": "dx",
      "group": "Clinical record/Operation and anaesthesia",
      "source": "clinical_data.csv",
      "value": {
        "kind": "not_applicable: categorical or identifier"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Discrete category or device size; not a resolution question.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/dx.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,388 records in 1038 categories (12 most common shown).",
          "alt": "Histogram of the recorded values of dx."
        }
      ],
      "derived": {}
    },
    "emergence": {
      "id": "emergence",
      "name": "Operation end to anaesthesia end",
      "summary": "Operation end to anaesthesia end (derived table).",
      "type": "clinical",
      "source_name": "emergence",
      "group": "Clinical record/Times and durations",
      "source": "derived",
      "unit": "min",
      "value": {
        "kind": "derived: computed from other variables' posteriors",
        "resolution": "derived from opend, aneend",
        "inputs": [
          "opend",
          "aneend"
        ],
        "formula": "(aneend - opend) / 60",
        "combination": "difference of the two event posteriors, sampled independently given the device references",
        "spread": "vitaldb-resolution/spread.json#emergence"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "derived": {}
    },
    "emop": {
      "id": "emop",
      "name": "Emergency operation",
      "summary": "Emergency operation (clinical table).",
      "type": "clinical",
      "source_name": "emop",
      "group": "Clinical record/Patient",
      "source": "clinical_data.csv",
      "value": {
        "kind": "not_applicable: categorical or identifier"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Discrete category or device size; not a resolution question.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/emop.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,388 records in 2 categories.",
          "alt": "Histogram of the recorded values of emop."
        }
      ],
      "derived": {}
    },
    "emr-events": {
      "id": "emr-events",
      "name": "Anaesthesia record events",
      "summary": "Anaesthesia and operation start and end times in the clinical table. Anaesthesia start marks the start of anaesthesia care, a median 14 minutes before device-marked induction ([[example:induction-hypotension]]).",
      "group": "Clinical record/Times and durations",
      "type": "event",
      "source_name": "clinical_data times",
      "device": "clinical-records",
      "stamp_clock": "hospital-records",
      "timing": [
        {
          "id": "emr5",
          "name": "5-minute entries",
          "default": true,
          "delay_s": [
            150.0,
            600.0
          ],
          "interval_s": [
            300.0,
            300.0
          ],
          "domain": "records",
          "status": "measured",
          "evidence": "rounded up: 90-290 s later than minute entries; ceiling kernel best in 11/11 device markers (small margin at anaesthesia start, where the value model prefers the nearest mark; op end floor by its value model)"
        },
        {
          "id": "emr1",
          "name": "minute entries",
          "delay_s": [
            30.0,
            300.0
          ],
          "interval_s": [
            60.0,
            60.0
          ],
          "domain": "records",
          "status": "estimated",
          "evidence": "minute grid, 5-min entries rounded up; anaesthesia start a median 14 min (p10-p90 6-25) before device induction (2,913 cases); per-entry documentation lag not measured"
        }
      ],
      "tags": [
        "stub"
      ],
      "chain": [
        {
          "node": "Hospital records clock",
          "clock": "hospital-records",
          "kind": "sync",
          "effect": "The laboratory and anaesthesia record systems keep hospital time (KST). Their offset from UTC and from the recorder PC is not observable in the release, which shifts every case to 2100.",
          "status": "assumed"
        },
        {
          "node": "Documentation",
          "kind": "processing",
          "status": "measured",
          "effect": "Entered in the anaesthesia record with a lag that is not measured separately; entries on a 5-minute mark were rounded up to it, so the event lies in the 5 minutes before, about 2.5 min earlier on average (clearly so at anaesthesia end; for the other times two studies disagree, see [[study:emr-rounding]]).",
          "offset_s": [
            0,
            300
          ],
          "parameter": "emr-rounding",
          "studies": [
            "emr-rounding"
          ]
        },
        {
          "node": "Stored clinical time",
          "kind": "stamping",
          "status": "measured",
          "offset_s": [
            60,
            300
          ],
          "effect": "On a 1-minute or 5-minute grid, relative to the case start."
        }
      ],
      "derived": {
        "parameters": [
          "emr-rounding"
        ],
        "studies": [
          "case-clock",
          "emr-rounding",
          "loop-closure"
        ],
        "observations": [
          "emr-offset-confounded",
          "emr-rounded-up",
          "event-track-generated"
        ],
        "examples": [
          "bolus-response-latency",
          "induction-hypotension"
        ]
      }
    },
    "esr": {
      "id": "esr",
      "name": "Erythrocyte sedimentation rate",
      "summary": "Erythrocyte sedimentation rate (lab table).",
      "type": "lab",
      "source_name": "esr",
      "group": "Laboratory/Haematology",
      "source": "lab_data.csv",
      "unit": "mm/hr",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "1 mm/hr, nearest",
        "step": 1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#esr"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; no heaping in the whole-number screen.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/esr.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 1,415 records, median 37 mm/hr; 6 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of esr."
        }
      ],
      "derived": {
        "observations": [
          "lab-reporting-limits"
        ]
      }
    },
    "ev1000-art-mbp": {
      "id": "ev1000-art-mbp",
      "name": "EV1000 mean arterial pressure",
      "summary": "Mean arterial pressure from the EV1000 platform, updated every 20 s.",
      "type": "numeric",
      "source_name": "EV1000/ART_MBP",
      "device": "ev1000",
      "stamp_clock": "recorder-pc",
      "unit": "mmHg",
      "timing": [
        {
          "id": "ev1000-sync",
          "name": "after MAP alignment",
          "default": true,
          "delay_s": [
            0.25,
            2.4
          ],
          "interval_s": [
            20.0,
            20.0
          ],
          "domain": "reference",
          "status": "measured",
          "evidence": "residual SD 0.25-2.4 s (p5-p95); three-way loop closes, median +0.13 s",
          "corrected_delay_s": [
            0.9,
            3.25
          ],
          "correction": "MAP alignment to SNUADC ART",
          "corrects": [
            "ev1000"
          ]
        },
        {
          "id": "ev1000",
          "name": "as stamped",
          "delay_s": [
            0.9,
            3.25
          ],
          "interval_s": [
            20.0,
            20.0
          ],
          "domain": "ev1000",
          "status": "measured",
          "evidence": "from the end of an assumed 20 s window: median 1.6 s (p25 0.9, p95 3.2; 579 cases)"
        }
      ],
      "tags": [
        "stub"
      ],
      "derived": {
        "studies": [
          "loop-closure",
          "rare-device-sync",
          "track-value-resolution"
        ]
      }
    },
    "fib": {
      "id": "fib",
      "name": "Fibrinogen",
      "summary": "Fibrinogen (lab table).",
      "type": "lab",
      "source_name": "fib",
      "group": "Laboratory/Coagulation",
      "source": "lab_data.csv",
      "unit": "mg/dL",
      "value": {
        "kind": "neighbours: computed value; interval to midpoints of neighbouring reachable values",
        "resolution": "computed value: interval to the neighbouring reachable values",
        "spread": "vitaldb-resolution/spread.json#fib"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Computed from a clotting time, so only some values are reachable; the interval runs to the midpoints of the neighbouring reachable values.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/fib.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 18,931 records, median 301 mg/dL; 187 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of fib."
        }
      ],
      "derived": {}
    },
    "fms-infusion": {
      "id": "fms-infusion",
      "name": "FMS rapid infuser",
      "summary": "Rapid infuser flow and volume.",
      "type": "numeric",
      "source_name": "FMS/FLOW_RATE",
      "device": "fms",
      "stamp_clock": "recorder-pc",
      "timing": [
        {
          "id": "invos-fms",
          "name": "as stamped",
          "default": true,
          "delay_s": [
            1.0,
            100.0
          ],
          "interval_s": [
            1.0,
            10.0
          ],
          "domain": "invos-fms",
          "status": "estimated",
          "evidence": "not synchronised; FMS flow start -> CVP rise 32 s median (plausible); Invos uninformative"
        }
      ],
      "tags": [
        "stub"
      ],
      "derived": {
        "studies": [
          "drug-effect-latency"
        ]
      }
    },
    "gfr": {
      "id": "gfr",
      "name": "Glomerular filtration rate",
      "summary": "Glomerular filtration rate (lab table).",
      "type": "lab",
      "source_name": "gfr",
      "group": "Laboratory/Chemistry",
      "source": "lab_data.csv",
      "unit": "mL/min/1.73 m2",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.1 mL/min/1.73 m2, nearest",
        "step": 0.1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#gfr"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; last digit flat.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/gfr.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 36,683 records, median 92.3 mL/min/1.73 m2; 358 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of gfr."
        }
      ],
      "derived": {}
    },
    "gluc": {
      "id": "gluc",
      "name": "Glucose",
      "summary": "Glucose (lab table).",
      "type": "lab",
      "source_name": "gluc",
      "group": "Laboratory/Electrolytes and glucose",
      "source": "lab_data.csv",
      "unit": "mg/dL",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "1 mg/dL, nearest",
        "step": 1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#gluc"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; no heaping in the whole-number screen.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/gluc.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 35,358 records, median 121 mg/dL; 346 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of gluc."
        }
      ],
      "derived": {}
    },
    "hb": {
      "id": "hb",
      "name": "Hemoglobin",
      "summary": "Hemoglobin (lab table).",
      "type": "lab",
      "source_name": "hb",
      "group": "Laboratory/Haematology",
      "source": "lab_data.csv",
      "unit": "g/dL",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.1 g/dL, nearest",
        "step": 0.1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#hb"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; last digit flat.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/hb.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 43,976 records, median 10.9 g/dL; 428 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of hb."
        }
      ],
      "derived": {}
    },
    "hco3": {
      "id": "hco3",
      "name": "Bicarbonate",
      "summary": "Bicarbonate (lab table).",
      "type": "lab",
      "source_name": "hco3",
      "group": "Laboratory/Blood gas",
      "source": "lab_data.csv",
      "unit": "mmol/L",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.1 mmol/L, nearest",
        "step": 0.1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#hco3"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; last digit flat.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/hco3.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 14,578 records, median 25.4 mmol/L; 135 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of hco3."
        }
      ],
      "derived": {}
    },
    "hct": {
      "id": "hct",
      "name": "Hematocrit",
      "summary": "Hematocrit (lab table).",
      "type": "lab",
      "source_name": "hct",
      "group": "Laboratory/Haematology",
      "source": "lab_data.csv",
      "unit": "%",
      "value": {
        "kind": "source_dependent: resolution chosen by a covariate (analyser)",
        "resolution": "by analyser: cbc 0.1 %, blood_gas 1 %",
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#hct"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Two analysers distinguishable record by record; neither is heaped.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/hct.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 58,498 records, median 33 %; 550 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of hct."
        }
      ],
      "derived": {}
    },
    "height": {
      "id": "height",
      "name": "Height",
      "summary": "Height (clinical table).",
      "type": "clinical",
      "source_name": "height",
      "group": "Clinical record/Patient",
      "source": "clinical_data.csv",
      "unit": "cm",
      "value": {
        "kind": "model: fitted rounding model (value kind); posterior over the true value",
        "resolution": "heaped: a mixture of rounding modes (see the value model)",
        "spread": "vitaldb-resolution/spread.json#height",
        "model": "vitaldb-resolution/vitaldb_resolution/data/models/height.model.json",
        "status": "measured",
        "studies": [
          "value-model-height"
        ]
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/height.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,388 records, median 162.2 cm; 63 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of height."
        }
      ],
      "derived": {
        "studies": [
          "value-model-height"
        ]
      }
    },
    "ica": {
      "id": "ica",
      "name": "Ionized Calcium",
      "summary": "ionized Calcium (lab table).",
      "type": "lab",
      "source_name": "ica",
      "group": "Laboratory/Electrolytes and glucose",
      "source": "lab_data.csv",
      "unit": "mmol/L",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.01 mmol/L, nearest",
        "step": 0.01,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#ica"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; last digit flat.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/ica.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 17,705 records, median 1.13 mmol/L; 168 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of ica."
        }
      ],
      "derived": {}
    },
    "icu-days": {
      "id": "icu-days",
      "name": "ICU days",
      "summary": "ICU days (clinical table).",
      "type": "clinical",
      "source_name": "icu_days",
      "group": "Clinical record/Outcome",
      "source": "clinical_data.csv",
      "unit": "days",
      "value": {
        "kind": "triangular_count: day count with ±1 uncertainty",
        "resolution": "whole days, counting rule unknown (±1)",
        "anchor_basis": "assumption",
        "anchor_note": "815 one-day stays against 98 two-day stays argue against calendar days touched; read as nights or completed days.",
        "spread": "vitaldb-resolution/spread.json#icu_days"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Day count, read as nights or completed days: 815 one-day stays against 98 two-day stays argue against calendar days touched. The rule is an assumption, so ±1 day is kept.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/icu-days.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,388 records, median 0 days; 29 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of icu_days."
        }
      ],
      "derived": {
        "observations": [
          "icu-days-exceed-postop-stay"
        ]
      }
    },
    "ind-to-incision": {
      "id": "ind-to-incision",
      "name": "Anaesthesia start to incision",
      "summary": "Anaesthesia start to incision (derived table).",
      "type": "clinical",
      "source_name": "ind_to_incision",
      "group": "Clinical record/Times and durations",
      "source": "derived",
      "unit": "min",
      "value": {
        "kind": "derived: computed from other variables' posteriors",
        "resolution": "derived from anestart, opstart",
        "inputs": [
          "anestart",
          "opstart"
        ],
        "formula": "(opstart - anestart) / 60",
        "combination": "difference of the two event posteriors, sampled independently given the device references",
        "spread": "vitaldb-resolution/spread.json#ind_to_incision"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "derived": {}
    },
    "intraop-ca": {
      "id": "intraop-ca",
      "name": "Calcium chloride",
      "summary": "Calcium chloride (clinical table).",
      "type": "clinical",
      "source_name": "intraop_ca",
      "group": "Clinical record/Drugs",
      "source": "clinical_data.csv",
      "unit": "mg",
      "value": {
        "kind": "exact: exact count; point mass",
        "resolution": "exact",
        "spread": "vitaldb-resolution/spread.json#intraop_ca"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Recorded total is a count of standard doses or ampoules; treated as exact.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/intraop-ca.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 868 records, median 600 mg; 10 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of intraop_ca."
        }
      ],
      "derived": {}
    },
    "intraop-colloid": {
      "id": "intraop-colloid",
      "name": "Intraoperative colloid",
      "summary": "Intraoperative colloid (clinical table).",
      "type": "clinical",
      "source_name": "intraop_colloid",
      "group": "Clinical record/Fluids and blood",
      "source": "clinical_data.csv",
      "unit": "mL",
      "value": {
        "kind": "colloid_rules: rule set by operation",
        "step": 1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Native recording unit, rounding to the nearest assumed; not tested (sparse or exact-count data).",
        "spread": "vitaldb-resolution/spread.json#intraop_colloid"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Container sizes are exact counts; partial amounts estimated. Kidney question pending.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/intraop-colloid.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 531 records, median 500 mL; 2 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of intraop_colloid."
        }
      ],
      "derived": {
        "examples": [
          "fluid-balance"
        ]
      }
    },
    "intraop-crystalloid": {
      "id": "intraop-crystalloid",
      "name": "Intraoperative crystalloid",
      "summary": "Intraoperative crystalloid (clinical table).",
      "type": "clinical",
      "source_name": "intraop_crystalloid",
      "group": "Clinical record/Fluids and blood",
      "source": "clinical_data.csv",
      "unit": "mL",
      "value": {
        "kind": "model: fitted rounding model (value kind); posterior over the true value",
        "resolution": "heaped: a mixture of rounding modes (see the value model)",
        "anchor": "nearest",
        "anchor_basis": "tested",
        "anchor_note": "Nearest rounding preferred to floor (H1, last graduation passed) by BIC: 47 with the duration covariate, 12 without (same 5863 records; results/2026-10-18_refits_duration_zero.md).",
        "spread": "vitaldb-resolution/spread.json#intraop_crystalloid",
        "model": "vitaldb-resolution/vitaldb_resolution/data/models/intraop_crystalloid.model.json",
        "status": "measured",
        "studies": [
          "value-model-intraop-crystalloid"
        ]
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/intraop-crystalloid.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 5,980 records, median 700 mL; 34 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of intraop_crystalloid."
        }
      ],
      "derived": {
        "studies": [
          "value-model-intraop-crystalloid"
        ],
        "examples": [
          "fluid-balance"
        ]
      }
    },
    "intraop-ebl": {
      "id": "intraop-ebl",
      "name": "Estimated blood loss",
      "summary": "Estimated blood loss (clinical table).",
      "type": "clinical",
      "source_name": "intraop_ebl",
      "group": "Clinical record/Fluids and blood",
      "source": "clinical_data.csv",
      "unit": "mL",
      "value": {
        "kind": "model: fitted rounding model (value kind); posterior over the true value",
        "resolution": "heaped: a mixture of rounding modes (see the value model)",
        "anchor": "nearest",
        "anchor_basis": "tested",
        "anchor_note": "Nearest rounding preferred to floor (H1, last graduation passed) by BIC: 52 with the duration covariate, 25 without (same 3882 records; results/2026-10-18_refits_duration_zero.md).",
        "spread": "vitaldb-resolution/spread.json#intraop_ebl",
        "model": "vitaldb-resolution/vitaldb_resolution/data/models/intraop_ebl.model.json",
        "status": "measured",
        "studies": [
          "value-model-intraop-ebl"
        ]
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/intraop-ebl.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 3,976 records, median 150 mL; 28 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of intraop_ebl."
        }
      ],
      "derived": {
        "studies": [
          "value-model-intraop-ebl"
        ],
        "examples": [
          "fluid-balance"
        ]
      }
    },
    "intraop-eph": {
      "id": "intraop-eph",
      "name": "Ephedrine",
      "summary": "Ephedrine (clinical table).",
      "type": "clinical",
      "source_name": "intraop_eph",
      "group": "Clinical record/Drugs",
      "source": "clinical_data.csv",
      "unit": "mg",
      "value": {
        "kind": "exact: exact count; point mass",
        "resolution": "exact",
        "spread": "vitaldb-resolution/spread.json#intraop_eph"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Recorded total is a count of standard doses or ampoules; treated as exact.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/intraop-eph.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 3,211 records, median 10 mg; 19 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of intraop_eph."
        }
      ],
      "derived": {}
    },
    "intraop-epi": {
      "id": "intraop-epi",
      "name": "Epinephrine",
      "summary": "Epinephrine (clinical table).",
      "type": "clinical",
      "source_name": "intraop_epi",
      "group": "Clinical record/Drugs",
      "source": "clinical_data.csv",
      "unit": "mcg",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "1 mcg, nearest",
        "step": 1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Native recording unit, rounding to the nearest assumed; not tested (sparse or exact-count data).",
        "spread": "vitaldb-resolution/spread.json#intraop_epi"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Sparse (80, 12 and 89 non-zero); native resolution only.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/intraop-epi.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 89 records, median 60 mcg; 2 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of intraop_epi."
        }
      ],
      "derived": {}
    },
    "intraop-ffp": {
      "id": "intraop-ffp",
      "name": "Intraoperative FFP transfusion",
      "summary": "Intraoperative FFP transfusion (clinical table).",
      "type": "clinical",
      "source_name": "intraop_ffp",
      "group": "Clinical record/Fluids and blood",
      "source": "clinical_data.csv",
      "unit": "Unit",
      "value": {
        "kind": "exact: exact count; point mass",
        "resolution": "exact",
        "spread": "vitaldb-resolution/spread.json#intraop_ffp"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Unit count; large values go with very large blood loss.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/intraop-ffp.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 129 records, median 4 Unit; 1 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of intraop_ffp."
        }
      ],
      "derived": {}
    },
    "intraop-ftn": {
      "id": "intraop-ftn",
      "name": "Fentanyl",
      "summary": "Fentanyl (clinical table).",
      "type": "clinical",
      "source_name": "intraop_ftn",
      "group": "Clinical record/Drugs",
      "source": "clinical_data.csv",
      "unit": "mcg",
      "value": {
        "kind": "exact: exact count; point mass",
        "resolution": "exact",
        "spread": "vitaldb-resolution/spread.json#intraop_ftn"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Recorded total is a count of standard doses or ampoules; treated as exact.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/intraop-ftn.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 1,168 records, median 100 mcg; 3 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of intraop_ftn."
        }
      ],
      "derived": {}
    },
    "intraop-mdz": {
      "id": "intraop-mdz",
      "name": "Midazolam",
      "summary": "Midazolam (clinical table).",
      "type": "clinical",
      "source_name": "intraop_mdz",
      "group": "Clinical record/Drugs",
      "source": "clinical_data.csv",
      "unit": "mg",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.5 mg, nearest",
        "step": 0.5,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Native recording unit, rounding to the nearest assumed; not tested (sparse or exact-count data).",
        "spread": "vitaldb-resolution/spread.json#intraop_mdz"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Sparse (80, 12 and 89 non-zero); native resolution only.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/intraop-mdz.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 80 records, median 3 mg; 1 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of intraop_mdz."
        }
      ],
      "derived": {}
    },
    "intraop-phe": {
      "id": "intraop-phe",
      "name": "Phenylephrine",
      "summary": "Phenylephrine (clinical table).",
      "type": "clinical",
      "source_name": "intraop_phe",
      "group": "Clinical record/Drugs",
      "source": "clinical_data.csv",
      "unit": "mcg",
      "value": {
        "kind": "exact: exact count; point mass",
        "resolution": "exact",
        "spread": "vitaldb-resolution/spread.json#intraop_phe"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Recorded total is a count of standard doses or ampoules; treated as exact.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/intraop-phe.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 844 records, median 100 mcg; 7 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of intraop_phe."
        }
      ],
      "derived": {}
    },
    "intraop-ppf": {
      "id": "intraop-ppf",
      "name": "Propofol bolus",
      "summary": "Propofol bolus (clinical table).",
      "type": "clinical",
      "source_name": "intraop_ppf",
      "group": "Clinical record/Drugs",
      "source": "clinical_data.csv",
      "unit": "mg",
      "value": {
        "kind": "model: fitted rounding model (value kind); posterior over the true value",
        "resolution": "heaped: a mixture of rounding modes (see the value model)",
        "spread": "vitaldb-resolution/spread.json#intraop_ppf",
        "model": "vitaldb-resolution/vitaldb_resolution/data/models/intraop_ppf.model.json",
        "status": "measured",
        "studies": [
          "value-model-intraop-ppf"
        ]
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single weight-based induction dose written as a preferred value. Zero in 3,719 of 6,043 GAs (TIVA is on the pump tracks, not here).",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/intraop-ppf.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 2,335 records, median 100 mg; 17 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of intraop_ppf."
        }
      ],
      "derived": {
        "studies": [
          "value-model-intraop-ppf"
        ],
        "examples": [
          "propofol-against-pump"
        ]
      }
    },
    "intraop-rbc": {
      "id": "intraop-rbc",
      "name": "Intraoperative RBC transfusion",
      "summary": "Intraoperative RBC transfusion (clinical table).",
      "type": "clinical",
      "source_name": "intraop_rbc",
      "group": "Clinical record/Fluids and blood",
      "source": "clinical_data.csv",
      "unit": "Unit",
      "value": {
        "kind": "exact: exact count; point mass",
        "resolution": "exact",
        "spread": "vitaldb-resolution/spread.json#intraop_rbc"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Unit count; counts fall smoothly, no heaping.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/intraop-rbc.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 352 records, median 2 Unit; 2 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of intraop_rbc."
        }
      ],
      "derived": {}
    },
    "intraop-rocu": {
      "id": "intraop-rocu",
      "name": "Rocuronium",
      "summary": "Rocuronium (clinical table).",
      "type": "clinical",
      "source_name": "intraop_rocu",
      "group": "Clinical record/Drugs",
      "source": "clinical_data.csv",
      "unit": "mg",
      "value": {
        "kind": "exact: exact count; point mass",
        "resolution": "exact",
        "spread": "vitaldb-resolution/spread.json#intraop_rocu"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Recorded total is a count of standard doses or ampoules; treated as exact.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/intraop-rocu.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 5,942 records, median 70 mg; 49 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of intraop_rocu."
        }
      ],
      "derived": {}
    },
    "intraop-uo": {
      "id": "intraop-uo",
      "name": "Intraoperative urine output",
      "summary": "Intraoperative urine output (clinical table).",
      "type": "clinical",
      "source_name": "intraop_uo",
      "group": "Clinical record/Fluids and blood",
      "source": "clinical_data.csv",
      "unit": "mL",
      "value": {
        "kind": "model: fitted rounding model (value kind); posterior over the true value",
        "resolution": "heaped: a mixture of rounding modes (see the value model)",
        "anchor": "nearest",
        "anchor_basis": "tested",
        "anchor_note": "Nearest rounding preferred to floor (H1, last graduation passed) by BIC: 383 with the duration covariate, 317 without (same 3630 records; results/2026-10-18_refits_duration_zero.md).",
        "spread": "vitaldb-resolution/spread.json#intraop_uo",
        "model": "vitaldb-resolution/vitaldb_resolution/data/models/intraop_uo.model.json",
        "status": "measured",
        "studies": [
          "value-model-intraop-uo"
        ]
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/intraop-uo.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 3,707 records, median 160 mL; 19 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of intraop_uo."
        }
      ],
      "derived": {
        "studies": [
          "value-model-intraop-uo"
        ],
        "examples": [
          "fluid-balance",
          "oliguria-labels",
          "regression-with-rounding",
          "urine-output-per-kg-per-hour"
        ]
      }
    },
    "intraop-vecu": {
      "id": "intraop-vecu",
      "name": "Vecuronium",
      "summary": "Vecuronium (clinical table).",
      "type": "clinical",
      "source_name": "intraop_vecu",
      "group": "Clinical record/Drugs",
      "source": "clinical_data.csv",
      "unit": "mg",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "1 mg, nearest",
        "step": 1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Native recording unit, rounding to the nearest assumed; not tested (sparse or exact-count data).",
        "spread": "vitaldb-resolution/spread.json#intraop_vecu"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Sparse (80, 12 and 89 non-zero); native resolution only.",
      "derived": {}
    },
    "invos-rso2": {
      "id": "invos-rso2",
      "name": "Invos regional oxygen saturation",
      "summary": "Cerebral oximetry; no signal shared with the reference.",
      "type": "numeric",
      "source_name": "Invos/SCO2_L",
      "device": "invos",
      "stamp_clock": "recorder-pc",
      "unit": "%",
      "timing": [
        {
          "id": "invos-fms",
          "name": "as stamped",
          "default": true,
          "delay_s": [
            1.0,
            100.0
          ],
          "interval_s": [
            1.0,
            10.0
          ],
          "domain": "invos-fms",
          "status": "estimated",
          "evidence": "not synchronised; FMS flow start -> CVP rise 32 s median (plausible); Invos uninformative"
        }
      ],
      "tags": [
        "stub"
      ],
      "derived": {
        "studies": [
          "drug-effect-latency"
        ],
        "examples": [
          "cerebral-autoregulation"
        ]
      }
    },
    "iv1": {
      "id": "iv1",
      "name": "Site of IV line (1)",
      "summary": "Site of IV line (1) (clinical table).",
      "type": "clinical",
      "source_name": "iv1",
      "group": "Clinical record/Airway and lines",
      "source": "clinical_data.csv",
      "value": {
        "kind": "not_applicable: categorical or identifier"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Discrete category or device size; not a resolution question.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/iv1.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,313 records in 11 categories.",
          "alt": "Histogram of the recorded values of iv1."
        }
      ],
      "derived": {}
    },
    "iv2": {
      "id": "iv2",
      "name": "Site of IV line (2)",
      "summary": "Site of IV line (2) (clinical table).",
      "type": "clinical",
      "source_name": "iv2",
      "group": "Clinical record/Airway and lines",
      "source": "clinical_data.csv",
      "value": {
        "kind": "not_applicable: categorical or identifier"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Discrete category or device size; not a resolution question.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/iv2.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 1,437 records in 11 categories.",
          "alt": "Histogram of the recorded values of iv2."
        }
      ],
      "derived": {}
    },
    "k": {
      "id": "k",
      "name": "Potassium",
      "summary": "Potassium (lab table).",
      "type": "lab",
      "source_name": "k",
      "group": "Laboratory/Electrolytes and glucose",
      "source": "lab_data.csv",
      "unit": "mmol/L",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.1 mmol/L, nearest",
        "step": 0.1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#k"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; last digit flat.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/k.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 55,486 records, median 4 mmol/L; 516 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of k."
        }
      ],
      "derived": {}
    },
    "lab-blood-gas": {
      "id": "lab-blood-gas",
      "name": "Blood gas result time",
      "summary": "Time stamp of a blood gas result in the lab table.",
      "group": "Laboratory/Result times",
      "type": "event",
      "source_name": "lab_data (blood gas)",
      "device": "clinical-records",
      "stamp_clock": "hospital-records",
      "timing": [
        {
          "id": "lab-bg",
          "name": "lab timestamp",
          "default": true,
          "delay_s": [
            190.0,
            730.0
          ],
          "interval_s": [
            1.0,
            1.0
          ],
          "domain": "records",
          "status": "measured",
          "evidence": "3-12 min after the access (modes ~4, ~9 min); the value is known later"
        },
        {
          "id": "lab-draw",
          "name": "draw time from arterial access",
          "delay_s": [
            10.0,
            100.0
          ],
          "interval_s": [
            28.0,
            117.0
          ],
          "domain": "records",
          "status": "estimated",
          "evidence": "access 28-117 s (p10-p90); matched 0-15 min (gas) or 0-30 min (central) before"
        }
      ],
      "tags": [
        "stub"
      ],
      "chain": [
        {
          "node": "Hospital records clock",
          "clock": "hospital-records",
          "kind": "sync",
          "effect": "The laboratory and anaesthesia record systems keep hospital time (KST). Their offset from UTC and from the recorder PC is not observable in the release, which shifts every case to 2100.",
          "status": "assumed"
        },
        {
          "node": "Blood draw",
          "kind": "processing",
          "status": "measured",
          "effect": "Blood is drawn from the arterial line (an access, 28–117 s long) and analysed in theatre or sent on. The specimen is stamped 3–12 min after the draw; the lab_draw label marks the matched access.",
          "offset_s": [
            190,
            730
          ],
          "residual_s": [
            28,
            117
          ],
          "parameter": "lab-bg-lag",
          "studies": [
            "lab-timestamp-meaning",
            "arterial-line-accesses"
          ]
        },
        {
          "node": "Result known",
          "kind": "processing",
          "status": "assumed",
          "effect": "The result follows an unrecorded turnaround; a value is known no earlier than its timestamp plus that turnaround."
        },
        {
          "node": "Stored lab time",
          "kind": "stamping",
          "status": "measured",
          "offset_s": [
            1,
            1
          ],
          "effect": "Whole seconds, one time per specimen shared by every analyte of the panel."
        }
      ],
      "derived": {
        "parameters": [
          "lab-bg-lag"
        ],
        "studies": [
          "arterial-line-accesses",
          "lab-timestamp-meaning"
        ],
        "observations": [
          "lab-time-is-specimen"
        ],
        "examples": [
          "gas-gradients-at-the-draw"
        ]
      }
    },
    "lab-central": {
      "id": "lab-central",
      "name": "Central lab result time",
      "summary": "Time stamp of a central laboratory result in the lab table.",
      "group": "Laboratory/Result times",
      "type": "event",
      "source_name": "lab_data (central lab)",
      "device": "clinical-records",
      "stamp_clock": "hospital-records",
      "timing": [
        {
          "id": "lab-central",
          "name": "lab timestamp",
          "default": true,
          "delay_s": [
            480.0,
            1440.0
          ],
          "interval_s": [
            1.0,
            1.0
          ],
          "domain": "records",
          "status": "measured",
          "evidence": "~8-24 min after the access (median ~14; weak); result after an unrecorded turnaround"
        },
        {
          "id": "lab-draw",
          "name": "draw time from arterial access",
          "delay_s": [
            10.0,
            100.0
          ],
          "interval_s": [
            28.0,
            117.0
          ],
          "domain": "records",
          "status": "estimated",
          "evidence": "access 28-117 s (p10-p90); matched 0-15 min (gas) or 0-30 min (central) before"
        }
      ],
      "tags": [
        "stub"
      ],
      "chain": [
        {
          "node": "Hospital records clock",
          "clock": "hospital-records",
          "kind": "sync",
          "effect": "The laboratory and anaesthesia record systems keep hospital time (KST). Their offset from UTC and from the recorder PC is not observable in the release, which shifts every case to 2100.",
          "status": "assumed"
        },
        {
          "node": "Blood draw",
          "kind": "processing",
          "status": "estimated",
          "effect": "Drawn (often not from the arterial line), sent to the central lab and logged there about 8–24 min later.",
          "offset_s": [
            480,
            1440
          ],
          "parameter": "lab-central-lag",
          "studies": [
            "lab-timestamp-meaning"
          ]
        },
        {
          "node": "Result known",
          "kind": "processing",
          "status": "assumed",
          "effect": "The result follows an unrecorded turnaround."
        },
        {
          "node": "Stored lab time",
          "kind": "stamping",
          "status": "measured",
          "offset_s": [
            1,
            1
          ],
          "effect": "Whole seconds, one time per specimen."
        }
      ],
      "derived": {
        "parameters": [
          "lab-central-lag"
        ],
        "studies": [
          "arterial-line-accesses",
          "lab-timestamp-meaning"
        ],
        "observations": [
          "lab-time-is-specimen"
        ]
      }
    },
    "lab-dt": {
      "id": "lab-dt",
      "name": "Lab timestamp (seconds from casestart)",
      "summary": "Lab timestamp (seconds from casestart) (lab table).",
      "type": "time",
      "source_name": "lab_dt",
      "group": "Laboratory/Result times",
      "source": "lab_data.csv",
      "unit": "sec",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "1 sec, floor",
        "step": 1,
        "anchor": "floor",
        "anchor_basis": "convention",
        "anchor_note": "Clock truncation to the whole second; at most 1 s, negligible.",
        "spread": "vitaldb-resolution/spread.json#lab_dt"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Machine-generated per-specimen time, not the result time: central-lab panels are logged about 10 min or more after the draw (specimen at the lab); blood gases 3-12 min after it (point-of-care entry or lab logging). The result is known only after a turnaround the release does not record, so a lab value must not be used at this time (the availability rule).\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "coupling": {
        "status": "coupled",
        "note": "The value is itself a time: its value side and its time side are the same uncertainty."
      },
      "derived": {}
    },
    "lac": {
      "id": "lac",
      "name": "Lactate",
      "summary": "Lactate (lab table).",
      "type": "lab",
      "source_name": "lac",
      "group": "Laboratory/Blood gas",
      "source": "lab_data.csv",
      "unit": "mmol/L",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.1 mmol/L, nearest",
        "step": 0.1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#lac"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; last digit flat.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/lac.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 14,516 records, median 1.2 mmol/L; 15 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of lac."
        }
      ],
      "derived": {
        "observations": [
          "lab-reporting-limits"
        ]
      }
    },
    "lmasize": {
      "id": "lmasize",
      "name": "LMA size",
      "summary": "LMA size (clinical table).",
      "type": "clinical",
      "source_name": "lmasize",
      "group": "Clinical record/Airway and lines",
      "source": "clinical_data.csv",
      "value": {
        "kind": "not_applicable: categorical or identifier"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Discrete category or device size; not a resolution question.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/lmasize.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 102 records in 3 categories.",
          "alt": "Histogram of the recorded values of lmasize."
        }
      ],
      "derived": {}
    },
    "los": {
      "id": "los",
      "name": "Length of hospital stay (dis - adm)",
      "summary": "Length of hospital stay (dis - adm) (derived table).",
      "type": "clinical",
      "source_name": "los",
      "group": "Clinical record/Outcome",
      "source": "derived",
      "unit": "days",
      "value": {
        "kind": "derived: computed from other variables' posteriors",
        "resolution": "derived from adm, dis",
        "inputs": [
          "adm",
          "dis"
        ],
        "formula": "(dis - adm) / 86400",
        "combination": "triangular over n ± 1 days",
        "spread": "vitaldb-resolution/spread.json#los"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Both ends are dates covering a whole day.",
      "derived": {}
    },
    "na": {
      "id": "na",
      "name": "Sodium",
      "summary": "Sodium (lab table).",
      "type": "lab",
      "source_name": "na",
      "group": "Laboratory/Electrolytes and glucose",
      "source": "lab_data.csv",
      "unit": "mmol/L",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "1 mmol/L, nearest",
        "step": 1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#na"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; no heaping in the whole-number screen.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/na.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 55,463 records, median 138 mmol/L; 504 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of na."
        }
      ],
      "derived": {}
    },
    "op-duration": {
      "id": "op-duration",
      "name": "Operation duration",
      "summary": "Operation duration (derived table).",
      "type": "clinical",
      "source_name": "op_duration",
      "group": "Clinical record/Times and durations",
      "source": "derived",
      "unit": "min",
      "value": {
        "kind": "derived: computed from other variables' posteriors",
        "resolution": "derived from opstart, opend",
        "inputs": [
          "opstart",
          "opend"
        ],
        "formula": "(opend - opstart) / 60",
        "combination": "difference of the two event posteriors, sampled independently given the device references",
        "spread": "vitaldb-resolution/spread.json#op_duration"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "derived": {}
    },
    "opend": {
      "id": "opend",
      "name": "Operation end time",
      "summary": "Operation end time; from casestart (clinical table).",
      "type": "time",
      "source_name": "opend",
      "group": "Clinical record/Times and durations",
      "source": "clinical_data.csv",
      "unit": "sec",
      "value": {
        "kind": "timestamp_model: fitted reference-offset model; posterior over the true event time",
        "resolution": "heaped: a mixture of rounding modes (see the value model)",
        "spread": "vitaldb-resolution/spread.json#opend",
        "model": "vitaldb-resolution/vitaldb_resolution/data/models/opend.model.json",
        "status": "measured",
        "studies": [
          "value-model-opend"
        ]
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Charted clock minute modelled as an offset from the recording end (device time). See the Timestamps section.\n\nHow its time was recorded is described with [[variable:emr-events]].",
      "stamp_clock": "hospital-records",
      "coupling": {
        "status": "coupled",
        "note": "The value is itself a time: its value side and its time side are the same uncertainty."
      },
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/opend.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,388 records, median 8877 sec; 64 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of opend."
        }
      ],
      "derived": {
        "studies": [
          "value-model-opend"
        ],
        "observations": [
          "imputed-op-ends"
        ]
      }
    },
    "opname": {
      "id": "opname",
      "name": "Operation name",
      "summary": "Operation name (clinical table).",
      "type": "clinical",
      "source_name": "opname",
      "group": "Clinical record/Operation and anaesthesia",
      "source": "clinical_data.csv",
      "value": {
        "kind": "not_applicable: categorical or identifier"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Discrete category or device size; not a resolution question.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/opname.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,388 records in 241 categories (12 most common shown).",
          "alt": "Histogram of the recorded values of opname."
        }
      ],
      "derived": {}
    },
    "opstart": {
      "id": "opstart",
      "name": "Operation start time",
      "summary": "Operation start time; from casestart (clinical table).",
      "type": "time",
      "source_name": "opstart",
      "group": "Clinical record/Times and durations",
      "source": "clinical_data.csv",
      "unit": "sec",
      "value": {
        "kind": "timestamp_model: fitted reference-offset model; posterior over the true event time",
        "resolution": "heaped: a mixture of rounding modes (see the value model)",
        "spread": "vitaldb-resolution/spread.json#opstart",
        "model": "vitaldb-resolution/vitaldb_resolution/data/models/opstart.model.json",
        "status": "measured",
        "studies": [
          "value-model-opstart"
        ]
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Charted clock minute modelled as an offset from the recording start (device time). See the Timestamps section.\n\nHow its time was recorded is described with [[variable:emr-events]].",
      "stamp_clock": "hospital-records",
      "coupling": {
        "status": "coupled",
        "note": "The value is itself a time: its value side and its time side are the same uncertainty."
      },
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/opstart.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,388 records, median 2055 sec; 64 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of opstart."
        }
      ],
      "derived": {
        "studies": [
          "value-model-opstart"
        ]
      }
    },
    "optype": {
      "id": "optype",
      "name": "Surgery type",
      "summary": "Surgery type (clinical table).",
      "type": "clinical",
      "source_name": "optype",
      "group": "Clinical record/Operation and anaesthesia",
      "source": "clinical_data.csv",
      "value": {
        "kind": "not_applicable: categorical or identifier"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Discrete category or device size; not a resolution question.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/optype.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,388 records in 11 categories.",
          "alt": "Histogram of the recorded values of optype."
        }
      ],
      "derived": {}
    },
    "p": {
      "id": "p",
      "name": "Probably phosphorus (unconfirmed): not in lab_parameters.csv",
      "summary": "Probably phosphorus (unconfirmed): not in lab_parameters.csv; median 3.2, IQR 2.7-3.8, 5th-95th percentile 1.9-5.0, consistent with inorganic phosphorus in mg/dL. Name kept as released. (lab table).",
      "type": "lab",
      "source_name": "p",
      "group": "Laboratory/Electrolytes and glucose",
      "source": "lab_data.csv",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.1 , nearest",
        "step": 0.1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#p"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; last digit flat.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/p.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 16,440 records, median 3.2; 153 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of p."
        }
      ],
      "derived": {}
    },
    "pco2": {
      "id": "pco2",
      "name": "Partial pressure of CO2",
      "summary": "partial pressure of CO2 (lab table).",
      "type": "lab",
      "source_name": "pco2",
      "group": "Laboratory/Blood gas",
      "source": "lab_data.csv",
      "unit": "mmHg",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "1 mmHg, nearest",
        "step": 1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#pco2"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; no heaping in the whole-number screen.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/pco2.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 14,590 records, median 41 mmHg; 130 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of pco2."
        }
      ],
      "derived": {
        "examples": [
          "gas-gradients-at-the-draw"
        ]
      }
    },
    "ph": {
      "id": "ph",
      "name": "pH",
      "summary": "pH (lab table).",
      "type": "lab",
      "source_name": "ph",
      "group": "Laboratory/Blood gas",
      "source": "lab_data.csv",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.01 , nearest",
        "step": 0.01,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#ph"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; last digit flat.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/ph.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 14,597 records, median 7.4; 125 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of ph."
        }
      ],
      "derived": {}
    },
    "plt": {
      "id": "plt",
      "name": "Platelet count",
      "summary": "Platelet count (lab table).",
      "type": "lab",
      "source_name": "plt",
      "group": "Laboratory/Haematology",
      "source": "lab_data.csv",
      "unit": "×1000/mcL",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "1 ×1000/mcL, nearest",
        "step": 1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#plt"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; no heaping in the whole-number screen.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/plt.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 43,529 records, median 199 ×1000/mcL; 428 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of plt."
        }
      ],
      "derived": {}
    },
    "po2": {
      "id": "po2",
      "name": "Partial pressure of O2",
      "summary": "partial pressure of O2 (lab table).",
      "type": "lab",
      "source_name": "po2",
      "group": "Laboratory/Blood gas",
      "source": "lab_data.csv",
      "unit": "mmHg",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "1 mmHg, nearest",
        "step": 1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#po2"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; no heaping in the whole-number screen.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/po2.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 14,597 records, median 150 mmHg; 144 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of po2."
        }
      ],
      "derived": {}
    },
    "position": {
      "id": "position",
      "name": "Surgical position",
      "summary": "Surgical position (clinical table).",
      "type": "clinical",
      "source_name": "position",
      "group": "Clinical record/Operation and anaesthesia",
      "source": "clinical_data.csv",
      "value": {
        "kind": "not_applicable: categorical or identifier"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Discrete category or device size; not a resolution question.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/position.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,194 records in 10 categories.",
          "alt": "Histogram of the recorded values of position."
        }
      ],
      "derived": {}
    },
    "preop-alb": {
      "id": "preop-alb",
      "name": "Preoperative albumin",
      "summary": "Preoperative albumin (clinical table).",
      "type": "clinical",
      "source_name": "preop_alb",
      "group": "Laboratory/Preoperative labs",
      "source": "clinical_data.csv",
      "unit": "g/dL",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.1 g/dL, nearest",
        "step": 0.1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#preop_alb"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Pre-operative copy of a lab result; behaves like the lab data.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/preop-alb.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,016 records, median 4.2 g/dL; 41 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of preop_alb."
        }
      ],
      "derived": {}
    },
    "preop-alt": {
      "id": "preop-alt",
      "name": "Preoperative GPT",
      "summary": "Preoperative GPT (clinical table).",
      "type": "clinical",
      "source_name": "preop_alt",
      "group": "Laboratory/Preoperative labs",
      "source": "clinical_data.csv",
      "unit": "IU/L",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "1 IU/L, nearest",
        "step": 1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#preop_alt"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Pre-operative copy of a lab result; behaves like the lab data.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/preop-alt.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,024 records, median 18 IU/L; 53 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of preop_alt."
        }
      ],
      "derived": {}
    },
    "preop-aptt": {
      "id": "preop-aptt",
      "name": "Preoperative aPTT",
      "summary": "Preoperative aPTT (clinical table).",
      "type": "clinical",
      "source_name": "preop_aptt",
      "group": "Laboratory/Preoperative labs",
      "source": "clinical_data.csv",
      "unit": "sec",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.1 sec, nearest",
        "step": 0.1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#preop_aptt"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Pre-operative copy of a lab result; behaves like the lab data.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/preop-aptt.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 5,986 records, median 32.1 sec; 60 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of preop_aptt."
        }
      ],
      "derived": {}
    },
    "preop-ast": {
      "id": "preop-ast",
      "name": "Preoperative GOT",
      "summary": "Preoperative GOT (clinical table).",
      "type": "clinical",
      "source_name": "preop_ast",
      "group": "Laboratory/Preoperative labs",
      "source": "clinical_data.csv",
      "unit": "IU/L",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "1 IU/L, nearest",
        "step": 1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#preop_ast"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Pre-operative copy of a lab result; behaves like the lab data.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/preop-ast.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,022 records, median 20 IU/L; 47 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of preop_ast."
        }
      ],
      "derived": {}
    },
    "preop-be": {
      "id": "preop-be",
      "name": "Preoperative base excess",
      "summary": "Preoperative base excess (clinical table).",
      "type": "clinical",
      "source_name": "preop_be",
      "group": "Laboratory/Preoperative labs",
      "source": "clinical_data.csv",
      "unit": "mmol/L",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.1 mmol/L, nearest",
        "step": 0.1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#preop_be"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Pre-operative copy of a lab result; behaves like the lab data.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/preop-be.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 532 records, median -0.1 mmol/L; 6 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of preop_be."
        }
      ],
      "derived": {}
    },
    "preop-bun": {
      "id": "preop-bun",
      "name": "Preoperative blood urea nitrogen",
      "summary": "Preoperative blood urea nitrogen (clinical table).",
      "type": "clinical",
      "source_name": "preop_bun",
      "group": "Laboratory/Preoperative labs",
      "source": "clinical_data.csv",
      "unit": "mg/dL",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "1 mg/dL, nearest",
        "step": 1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#preop_bun"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Pre-operative copy of a lab result; behaves like the lab data.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/preop-bun.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,023 records, median 14 mg/dL; 44 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of preop_bun."
        }
      ],
      "derived": {}
    },
    "preop-cr": {
      "id": "preop-cr",
      "name": "Preoperative creatinine",
      "summary": "Preoperative creatinine (clinical table).",
      "type": "clinical",
      "source_name": "preop_cr",
      "group": "Laboratory/Preoperative labs",
      "source": "clinical_data.csv",
      "unit": "mg/dL",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.01 mg/dL, nearest",
        "step": 0.01,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#preop_cr"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Pre-operative copy of a lab result; behaves like the lab data.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/preop-cr.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,016 records, median 0.78 mg/dL; 59 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of preop_cr."
        }
      ],
      "derived": {
        "examples": [
          "regression-with-rounding"
        ]
      }
    },
    "preop-dm": {
      "id": "preop-dm",
      "name": "Preoperative diabetes",
      "summary": "Preoperative diabetes (clinical table).",
      "type": "clinical",
      "source_name": "preop_dm",
      "group": "Clinical record/Preoperative history",
      "source": "clinical_data.csv",
      "value": {
        "kind": "not_applicable: categorical or identifier"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Discrete category or device size; not a resolution question.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/preop-dm.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,388 records in 2 categories.",
          "alt": "Histogram of the recorded values of preop_dm."
        }
      ],
      "derived": {}
    },
    "preop-ecg": {
      "id": "preop-ecg",
      "name": "Preoperative ECG",
      "summary": "Preoperative ECG (clinical table).",
      "type": "clinical",
      "source_name": "preop_ecg",
      "group": "Clinical record/Preoperative history",
      "source": "clinical_data.csv",
      "value": {
        "kind": "not_applicable: categorical or identifier"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Discrete category or device size; not a resolution question.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/preop-ecg.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,388 records in 27 categories (12 most common shown).",
          "alt": "Histogram of the recorded values of preop_ecg."
        }
      ],
      "derived": {}
    },
    "preop-gluc": {
      "id": "preop-gluc",
      "name": "Preoperative glucose",
      "summary": "Preoperative glucose (clinical table).",
      "type": "clinical",
      "source_name": "preop_gluc",
      "group": "Laboratory/Preoperative labs",
      "source": "clinical_data.csv",
      "unit": "mg/dL",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "1 mg/dL, nearest",
        "step": 1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#preop_gluc"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Pre-operative copy of a lab result; behaves like the lab data.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/preop-gluc.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,010 records, median 103 mg/dL; 62 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of preop_gluc."
        }
      ],
      "derived": {}
    },
    "preop-hb": {
      "id": "preop-hb",
      "name": "Preoperative hemoglobin",
      "summary": "Preoperative hemoglobin (clinical table).",
      "type": "clinical",
      "source_name": "preop_hb",
      "group": "Laboratory/Preoperative labs",
      "source": "clinical_data.csv",
      "unit": "g/dL",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.1 g/dL, nearest",
        "step": 0.1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#preop_hb"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Pre-operative copy of a lab result; behaves like the lab data.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/preop-hb.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,047 records, median 13 g/dL; 56 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of preop_hb."
        }
      ],
      "derived": {}
    },
    "preop-hco3": {
      "id": "preop-hco3",
      "name": "Preoperative HCO3-",
      "summary": "Preoperative HCO3- (clinical table).",
      "type": "clinical",
      "source_name": "preop_hco3",
      "group": "Laboratory/Preoperative labs",
      "source": "clinical_data.csv",
      "unit": "mmol/L",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.1 mmol/L, nearest",
        "step": 0.1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#preop_hco3"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Pre-operative copy of a lab result; behaves like the lab data.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/preop-hco3.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 533 records, median 24.3 mmol/L; 4 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of preop_hco3."
        }
      ],
      "derived": {}
    },
    "preop-htn": {
      "id": "preop-htn",
      "name": "Preoperative hypertension",
      "summary": "Preoperative hypertension (clinical table).",
      "type": "clinical",
      "source_name": "preop_htn",
      "group": "Clinical record/Preoperative history",
      "source": "clinical_data.csv",
      "value": {
        "kind": "not_applicable: categorical or identifier"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Discrete category or device size; not a resolution question.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/preop-htn.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,388 records in 2 categories.",
          "alt": "Histogram of the recorded values of preop_htn."
        }
      ],
      "derived": {}
    },
    "preop-k": {
      "id": "preop-k",
      "name": "Preoperative K",
      "summary": "Preoperative K (clinical table).",
      "type": "clinical",
      "source_name": "preop_k",
      "group": "Laboratory/Preoperative labs",
      "source": "clinical_data.csv",
      "unit": "mmol/L",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.1 mmol/L, nearest",
        "step": 0.1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#preop_k"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Pre-operative copy of a lab result; behaves like the lab data.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/preop-k.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 5,767 records, median 4.2 mmol/L; 46 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of preop_k."
        }
      ],
      "derived": {}
    },
    "preop-na": {
      "id": "preop-na",
      "name": "Preoperative Na",
      "summary": "Preoperative Na (clinical table).",
      "type": "clinical",
      "source_name": "preop_na",
      "group": "Laboratory/Preoperative labs",
      "source": "clinical_data.csv",
      "unit": "mmol/L",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "1 mmol/L, nearest",
        "step": 1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#preop_na"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Pre-operative copy of a lab result; behaves like the lab data.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/preop-na.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 5,765 records, median 140 mmol/L; 46 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of preop_na."
        }
      ],
      "derived": {}
    },
    "preop-paco2": {
      "id": "preop-paco2",
      "name": "Preoperative PaCO2",
      "summary": "Preoperative PaCO2 (clinical table).",
      "type": "clinical",
      "source_name": "preop_paco2",
      "group": "Laboratory/Preoperative labs",
      "source": "clinical_data.csv",
      "unit": "mmHg",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.1 mmHg, nearest",
        "step": 0.1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#preop_paco2"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Pre-operative copy of a lab result; behaves like the lab data.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/preop-paco2.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 538 records, median 39.5 mmHg; 6 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of preop_paco2."
        }
      ],
      "derived": {}
    },
    "preop-pao2": {
      "id": "preop-pao2",
      "name": "Preoperative PaO2",
      "summary": "Preoperative PaO2 (clinical table).",
      "type": "clinical",
      "source_name": "preop_pao2",
      "group": "Laboratory/Preoperative labs",
      "source": "clinical_data.csv",
      "unit": "mmHg",
      "value": {
        "kind": "decimal_dependent: resolution chosen by whether the record has a decimal",
        "resolution": "0.1 mmHg when a decimal is recorded, else 1 mmHg",
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#preop_pao2"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "41% end in .0 against 10% expected: two sources, decided by whether a decimal is present.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/preop-pao2.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 538 records, median 95.95 mmHg; 6 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of preop_pao2."
        }
      ],
      "derived": {}
    },
    "preop-pft": {
      "id": "preop-pft",
      "name": "Preoperative pulmonary function",
      "summary": "Preoperative pulmonary function (clinical table).",
      "type": "clinical",
      "source_name": "preop_pft",
      "group": "Clinical record/Preoperative history",
      "source": "clinical_data.csv",
      "value": {
        "kind": "not_applicable: categorical or identifier"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Discrete category or device size; not a resolution question.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/preop-pft.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,388 records in 9 categories.",
          "alt": "Histogram of the recorded values of preop_pft."
        }
      ],
      "derived": {}
    },
    "preop-ph": {
      "id": "preop-ph",
      "name": "Preoperative pH",
      "summary": "Preoperative pH (clinical table).",
      "type": "clinical",
      "source_name": "preop_ph",
      "group": "Laboratory/Preoperative labs",
      "source": "clinical_data.csv",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.001 , nearest",
        "step": 0.001,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#preop_ph"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Pre-operative copy of a lab result; behaves like the lab data.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/preop-ph.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 546 records, median 7.401; 6 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of preop_ph."
        }
      ],
      "derived": {}
    },
    "preop-plt": {
      "id": "preop-plt",
      "name": "Preoperative platelet count",
      "summary": "Preoperative platelet count (clinical table).",
      "type": "clinical",
      "source_name": "preop_plt",
      "group": "Laboratory/Preoperative labs",
      "source": "clinical_data.csv",
      "unit": "x1000/mcL",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "1 x1000/mcL, nearest",
        "step": 1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#preop_plt"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Pre-operative copy of a lab result; behaves like the lab data.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/preop-plt.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,047 records, median 235 x1000/mcL; 62 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of preop_plt."
        }
      ],
      "derived": {}
    },
    "preop-pt": {
      "id": "preop-pt",
      "name": "Preoperative PT",
      "summary": "Preoperative PT (clinical table).",
      "type": "clinical",
      "source_name": "preop_pt",
      "group": "Laboratory/Preoperative labs",
      "source": "clinical_data.csv",
      "unit": "%",
      "value": {
        "kind": "neighbours: computed value; interval to midpoints of neighbouring reachable values",
        "resolution": "computed value: interval to the neighbouring reachable values",
        "spread": "vitaldb-resolution/spread.json#preop_pt"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "PT %, computed; uses the lab reachable values.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/preop-pt.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 5,998 records, median 101 %; 55 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of preop_pt."
        }
      ],
      "derived": {}
    },
    "preop-sao2": {
      "id": "preop-sao2",
      "name": "Preoperative SpO2",
      "summary": "Preoperative SpO2 (clinical table).",
      "type": "clinical",
      "source_name": "preop_sao2",
      "group": "Laboratory/Preoperative labs",
      "source": "clinical_data.csv",
      "unit": "%",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.1 %, nearest",
        "step": 0.1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#preop_sao2"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Pre-operative copy of a lab result; behaves like the lab data.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/preop-sao2.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 533 records, median 97 %; 4 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of preop_sao2."
        }
      ],
      "derived": {}
    },
    "primus-awp": {
      "id": "primus-awp",
      "name": "Airway pressure (Primus)",
      "summary": "Airway pressure from the Primus anaesthesia machine at 62.5 Hz.",
      "type": "waveform",
      "source_name": "Primus/AWP",
      "device": "primus",
      "stamp_clock": "recorder-pc",
      "sample_clock": "primus-clock",
      "rate_hz": 62.5,
      "unit": "hPa",
      "timing": [
        {
          "id": "primus-sync",
          "name": "after breath matching",
          "default": true,
          "delay_s": [
            0.055,
            0.187
          ],
          "interval_s": [
            0.016,
            0.016
          ],
          "domain": "reference",
          "status": "measured",
          "evidence": "leave-one-out error 55 ms median, p90 187 ms, a line per stretch between AWP pauses (1,067 recordings with CVP, tools/primus_sync_accuracy.py); 74 ms with one slope per recording",
          "corrected_delay_s": [
            0.6,
            5.1
          ],
          "correction": "breath matching to SNUADC",
          "corrects": [
            "primus-raw"
          ]
        },
        {
          "id": "primus-raw",
          "name": "raw stamps",
          "delay_s": [
            0.6,
            5.1
          ],
          "interval_s": [
            0.016,
            0.016
          ],
          "domain": "primus",
          "status": "measured",
          "evidence": "offset p10-p90 0.6-5.1 s, median 1.8 s (1,061 recordings, examples/ppv-by-breath), incl. AWP-CVP physiology; drifts +104 ppm against the PC between AWP pauses"
        }
      ],
      "tags": [
        "stub"
      ],
      "derived": {
        "studies": [
          "airway-heartbeat-pilot",
          "continuity-gaps-segments",
          "correction-periods",
          "device-correction-schedules",
          "loop-closure",
          "numeric-derivation-pilot",
          "primus-pause-steps",
          "record-messages-and-steps",
          "step-classification",
          "track-value-resolution",
          "ventilator-breath-matching"
        ],
        "observations": [
          "duplicated-records",
          "primus-awp-pauses",
          "primus-waveform-gains"
        ],
        "examples": [
          "ppv-by-breath"
        ]
      }
    },
    "primus-co2": {
      "id": "primus-co2",
      "name": "Capnogram (Primus)",
      "summary": "Sidestream CO2 from the Primus at 62.5 Hz; the sampling line adds at least 1 s.",
      "type": "waveform",
      "source_name": "Primus/CO2",
      "device": "primus",
      "stamp_clock": "recorder-pc",
      "sample_clock": "primus-clock",
      "rate_hz": 62.5,
      "unit": "mmHg",
      "timing": [
        {
          "id": "primus-sync",
          "name": "after breath matching",
          "default": true,
          "delay_s": [
            0.055,
            0.187
          ],
          "interval_s": [
            0.016,
            0.016
          ],
          "domain": "reference",
          "status": "measured",
          "evidence": "leave-one-out error 55 ms median, p90 187 ms, a line per stretch between AWP pauses (1,067 recordings with CVP, tools/primus_sync_accuracy.py); 74 ms with one slope per recording",
          "corrected_delay_s": [
            0.6,
            5.1
          ],
          "correction": "breath matching to SNUADC",
          "corrects": [
            "primus-raw"
          ]
        },
        {
          "id": "primus-raw",
          "name": "raw stamps",
          "delay_s": [
            0.6,
            5.1
          ],
          "interval_s": [
            0.016,
            0.016
          ],
          "domain": "primus",
          "status": "measured",
          "evidence": "offset p10-p90 0.6-5.1 s, median 1.8 s (1,061 recordings, examples/ppv-by-breath), incl. AWP-CVP physiology; drifts +104 ppm against the PC between AWP pauses"
        }
      ],
      "tags": [
        "stub"
      ],
      "derived": {
        "studies": [
          "airway-heartbeat-pilot",
          "clock-correction-scan",
          "continuity-gaps-segments",
          "device-correction-schedules",
          "numeric-derivation-pilot",
          "primus-pause-steps",
          "record-messages-and-steps",
          "step-classification",
          "track-value-resolution",
          "ventilator-breath-matching"
        ],
        "observations": [
          "co2-cardiac-trace",
          "duplicated-records",
          "primus-awp-pauses",
          "primus-waveform-gains"
        ]
      }
    },
    "primus-numerics": {
      "id": "primus-numerics",
      "name": "Primus ETCO2, PIP and RR",
      "summary": "End-tidal CO2, peak pressure and respiratory rate from the Primus, polled about every 7 s.",
      "type": "numeric",
      "source_name": "Primus/ETCO2",
      "device": "primus",
      "stamp_clock": "recorder-pc",
      "timing": [
        {
          "id": "primus-lab",
          "name": "after the per-case delay label",
          "default": true,
          "delay_s": [
            0.07,
            0.57
          ],
          "interval_s": [
            7.0,
            30.0
          ],
          "domain": "primus",
          "status": "measured",
          "evidence": "numeric_delay label SE p10-p90 0.07-0.57 s (RR_CO2 weaker: SE ~1 s)",
          "corrected_delay_s": [
            0.75,
            7.6
          ],
          "correction": "per-case delay label",
          "corrects": [
            "primus-num"
          ]
        },
        {
          "id": "primus-num",
          "name": "as stamped",
          "delay_s": [
            0.75,
            7.6
          ],
          "interval_s": [
            7.0,
            30.0
          ],
          "domain": "primus",
          "status": "measured",
          "evidence": "from window end: ETCO2 0.75-3.4, PIP 3.65-6.5, RR 1.6-7.6 s; 7 s polls, RR over 30 s"
        }
      ],
      "tags": [
        "stub"
      ],
      "delays": [
        {
          "name": "ETCO2",
          "parameter": "etco2-delay",
          "description": "Value of the last completed breath."
        },
        {
          "name": "PIP",
          "parameter": "pip-delay",
          "description": "Value of the last completed breath, from the end of inspiration."
        },
        {
          "name": "RR",
          "parameter": "rr-delay",
          "description": "60 / mean breath interval over 30 s."
        }
      ],
      "derived": {
        "parameters": [
          "etco2-delay",
          "pip-delay",
          "rr-delay"
        ],
        "studies": [
          "emr-rounding",
          "numeric-delay-variability",
          "numeric-derivation-pilot",
          "track-value-resolution"
        ],
        "observations": [
          "numeric-delays-per-case",
          "numerics-not-front-stamped",
          "primus-waveform-gains"
        ],
        "examples": [
          "gas-gradients-at-the-draw"
        ]
      }
    },
    "pt-pct": {
      "id": "pt-pct",
      "name": "Prothrombin time (%)",
      "summary": "Prothrombin time (%) (lab table).",
      "type": "lab",
      "source_name": "pt%",
      "group": "Laboratory/Coagulation",
      "source": "lab_data.csv",
      "unit": "%",
      "value": {
        "kind": "neighbours: computed value; interval to midpoints of neighbouring reachable values",
        "resolution": "computed value: interval to the neighbouring reachable values",
        "spread": "vitaldb-resolution/spread.json#pt%"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Computed from a clotting time, so only some values are reachable; the interval runs to the midpoints of the neighbouring reachable values.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/pt-pct.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 21,154 records, median 85 %; 195 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of pt%."
        }
      ],
      "derived": {}
    },
    "ptinr": {
      "id": "ptinr",
      "name": "Prothrombin time (INR)",
      "summary": "Prothrombin time (INR) (lab table).",
      "type": "lab",
      "source_name": "ptinr",
      "group": "Laboratory/Coagulation",
      "source": "lab_data.csv",
      "unit": "INR",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.01 INR, nearest",
        "step": 0.01,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#ptinr"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; last digit flat.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/ptinr.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 21,156 records, median 1.11 INR; 180 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of ptinr."
        }
      ],
      "derived": {}
    },
    "ptsec": {
      "id": "ptsec",
      "name": "Prothrombin time (sec)",
      "summary": "Prothrombin time (sec) (lab table).",
      "type": "lab",
      "source_name": "ptsec",
      "group": "Laboratory/Coagulation",
      "source": "lab_data.csv",
      "unit": "sec",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.1 sec, nearest",
        "step": 0.1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#ptsec"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; last digit flat.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/ptsec.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 21,156 records, median 12.6 sec; 198 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of ptsec."
        }
      ],
      "derived": {}
    },
    "pump-rates": {
      "id": "pump-rates",
      "name": "Infusion pump rates and volumes",
      "summary": "Drug infusion rates and volumes from the pumps.",
      "type": "numeric",
      "source_name": "Orchestra/PPF20_RATE",
      "device": "orchestra",
      "stamp_clock": "recorder-pc",
      "timing": [
        {
          "id": "pumps",
          "name": "as stamped",
          "default": true,
          "delay_s": [
            0.1,
            2.0
          ],
          "interval_s": [
            1.0,
            10.0
          ],
          "domain": "pumps",
          "status": "estimated",
          "evidence": "1 s records; propofol start -> BIS<60 132 s median, none negative (497 cases)"
        }
      ],
      "tags": [
        "stub"
      ],
      "derived": {
        "studies": [
          "drug-effect-latency",
          "emr-rounding",
          "track-value-resolution"
        ],
        "examples": [
          "induction-hypotension",
          "propofol-against-pump"
        ]
      }
    },
    "sao2": {
      "id": "sao2",
      "name": "Arterial oxygen saturation",
      "summary": "Arterial oxygen saturation (lab table).",
      "type": "lab",
      "source_name": "sao2",
      "group": "Laboratory/Blood gas",
      "source": "lab_data.csv",
      "unit": "%",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "1 %, nearest",
        "step": 1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#sao2"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; no heaping in the whole-number screen.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/sao2.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 14,572 records, median 99 %; 72 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of sao2."
        }
      ],
      "derived": {
        "examples": [
          "gas-gradients-at-the-draw"
        ]
      }
    },
    "sex": {
      "id": "sex",
      "name": "Sex",
      "summary": "Sex (clinical table).",
      "type": "clinical",
      "source_name": "sex",
      "group": "Clinical record/Patient",
      "source": "clinical_data.csv",
      "unit": "M/F",
      "value": {
        "kind": "not_applicable: categorical or identifier"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/sex.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,388 records in 2 categories.",
          "alt": "Histogram of the recorded values of sex."
        }
      ],
      "derived": {}
    },
    "snuadc-art": {
      "id": "snuadc-art",
      "name": "Arterial pressure (SNUADC)",
      "summary": "Invasive arterial pressure, sampled by SNUADC at 500 Hz from the monitor's analogue output.",
      "type": "waveform",
      "source_name": "SNUADC/ART",
      "device": "snuadc",
      "stamp_clock": "recorder-pc",
      "sample_clock": "snuadc-sample",
      "rate_hz": 500,
      "unit": "mmHg",
      "timing": [
        {
          "id": "snu-model",
          "name": "stamp-model time",
          "default": true,
          "delay_s": [
            0.0003,
            0.003
          ],
          "interval_s": [
            0.002,
            0.01
          ],
          "domain": "reference",
          "status": "estimated",
          "evidence": "stamp-model residual where corrections are seen (recordings with none can drift about +-0.1 s/h on the port's rate); 100 Hz files 10 ms",
          "corrects": [
            "snu-raw"
          ]
        },
        {
          "id": "snu-raw",
          "name": "raw stamps",
          "delay_s": [
            0.01,
            0.48
          ],
          "interval_s": [
            0.002,
            0.002
          ],
          "domain": "snuadc-raw",
          "status": "measured",
          "evidence": "sawtooth up to -480 ms (step mode corrections)"
        }
      ],
      "tags": [
        "stub"
      ],
      "chain": [
        {
          "node": "SNUADC sample clock",
          "clock": "snuadc-sample",
          "kind": "clock",
          "effect": "SNUADC samples on its own crystal; the recorder stamps each 0.2 s record from its model of that clock. In step mode the stamps drift (−63 ppm median) and are pulled back about 0.48 s whenever the drift reaches half a second; in frequent-correction mode they stay within about 10 ms. The atlas's stamp model refits the line.",
          "offset_s": [
            0.01,
            0.48
          ],
          "residual_s": [
            0.0003,
            0.003
          ],
          "drift": "−63 ppm median in step mode, pulled back about every 2.08 h",
          "status": "measured",
          "link": "snuadc-step-vs-pc",
          "studies": [
            "snuadc-stamp-sawtooth"
          ]
        },
        {
          "node": "Signal lost at resets",
          "kind": "correction",
          "effect": "At each step-mode reset about 18–20 ms of signal is never sampled, while every record still holds 100 samples. The stamp model adds the loss back at the end of the last record before the new grid; samples inside that record can still be up to about 20 ms late.",
          "offset_s": [
            0.0172,
            0.0182
          ],
          "residual_s": [
            0.0,
            0.02
          ],
          "status": "measured",
          "parameter": "snuadc-reset-loss",
          "studies": [
            "paced-ecg"
          ]
        },
        {
          "node": "Monitor analogue output",
          "kind": "transport",
          "effect": "The arterial pressure reaches SNUADC as the patient monitor's analogue output. Any delay inside the monitor is common to the ECG and pressure channels and has not been measured.",
          "status": "assumed"
        },
        {
          "node": "Stored sample time",
          "kind": "stamping",
          "effect": "Sample j of a record sits at its stamp + j/500 s. Stamps run −0.95 ppm against the sample count (stamp arithmetic), and the five records after each reset are written out of order; the [[code:timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/connector.py|TimeNet import]] puts them back in sampling order.",
          "offset_s": [
            0.002,
            0.002
          ],
          "status": "measured"
        }
      ],
      "derived": {
        "parameters": [
          "line-access-rate"
        ],
        "studies": [
          "arterial-line-accesses",
          "clock-correction-scan",
          "continuity-gaps-segments",
          "lab-timestamp-meaning",
          "loop-closure",
          "monitor-lines",
          "numeric-derivation-pilot",
          "pat-offset-census",
          "ppg-art-reference",
          "ppg-frame-clock",
          "ppg-sawtooth-pauses",
          "rare-device-sync",
          "record-messages-and-steps",
          "reset-cluster-order",
          "snuadc-firmware",
          "solar-snuadc-clock",
          "step-classification",
          "track-value-resolution",
          "ventilator-breath-matching",
          "vitaldb-loader"
        ],
        "observations": [
          "art-with-pleth-factors",
          "arterial-steps-at-line-events",
          "cuff-occludes-line",
          "duplicated-records",
          "interleaved-records",
          "loaders-lose-samples",
          "reset-records-out-of-order",
          "reset-signal-lost",
          "reset-zero-sample",
          "small-snuadc-gaps",
          "snuadc-100hz-cases",
          "snuadc-channel-skew",
          "snuadc-no-timestamps",
          "snuadc-one-clock",
          "snuadc-ten-bit-codes",
          "snuadc-value-truncation"
        ],
        "examples": [
          "bolus-response-latency",
          "cerebral-autoregulation",
          "gas-gradients-at-the-draw",
          "hypotension-burden",
          "pat-vs-bp",
          "ppv-by-breath",
          "pulse-arrival-time"
        ]
      }
    },
    "snuadc-ecg-ii": {
      "id": "snuadc-ecg-ii",
      "name": "ECG lead II (SNUADC)",
      "summary": "Lead II of the ECG, sampled by SNUADC at 500 Hz from the monitor's analogue output.",
      "type": "waveform",
      "source_name": "SNUADC/ECG_II",
      "device": "snuadc",
      "stamp_clock": "recorder-pc",
      "sample_clock": "snuadc-sample",
      "rate_hz": 500,
      "unit": "mV",
      "description": "Samples are spaced by the [[clock:snuadc-sample]]; their record stamps come from the recorder's model of that\nclock, so they carry the drift and resets described in [[link:snuadc-step-vs-pc]]. At every step-mode reset\nabout 18 ms of signal is not sampled, although every record still holds 100 samples\n([[parameter:snuadc-reset-loss]]).\n",
      "timing": [
        {
          "id": "snu-model",
          "name": "stamp-model time",
          "default": true,
          "delay_s": [
            0.0003,
            0.003
          ],
          "interval_s": [
            0.002,
            0.01
          ],
          "domain": "reference",
          "status": "estimated",
          "evidence": "stamp-model residual where corrections are seen (recordings with none can drift about +-0.1 s/h on the port's rate); 100 Hz files 10 ms",
          "corrects": [
            "snu-raw"
          ]
        },
        {
          "id": "snu-raw",
          "name": "raw stamps",
          "delay_s": [
            0.01,
            0.48
          ],
          "interval_s": [
            0.002,
            0.002
          ],
          "domain": "snuadc-raw",
          "status": "measured",
          "evidence": "sawtooth up to -480 ms (step mode corrections)"
        }
      ],
      "chain": [
        {
          "node": "SNUADC sample clock",
          "clock": "snuadc-sample",
          "kind": "clock",
          "effect": "SNUADC samples on its own crystal; the recorder stamps each 0.2 s record from its model of that clock. In step mode the stamps drift (−63 ppm median) and are pulled back about 0.48 s whenever the drift reaches half a second; in frequent-correction mode they stay within about 10 ms. The atlas's stamp model refits the line.",
          "offset_s": [
            0.01,
            0.48
          ],
          "residual_s": [
            0.0003,
            0.003
          ],
          "drift": "−63 ppm median in step mode, pulled back about every 2.08 h",
          "status": "measured",
          "link": "snuadc-step-vs-pc",
          "studies": [
            "snuadc-stamp-sawtooth"
          ]
        },
        {
          "node": "Signal lost at resets",
          "kind": "correction",
          "effect": "At each step-mode reset about 18–20 ms of signal is never sampled, while every record still holds 100 samples. The stamp model adds the loss back at the end of the last record before the new grid; samples inside that record can still be up to about 20 ms late.",
          "offset_s": [
            0.0172,
            0.0182
          ],
          "residual_s": [
            0.0,
            0.02
          ],
          "status": "measured",
          "parameter": "snuadc-reset-loss",
          "studies": [
            "paced-ecg"
          ]
        },
        {
          "node": "Monitor analogue output",
          "kind": "transport",
          "effect": "The ECG reaches SNUADC as the patient monitor's analogue output. Any delay inside the monitor is common to the ECG and pressure channels and has not been measured.",
          "status": "assumed"
        },
        {
          "node": "Stored sample time",
          "kind": "stamping",
          "effect": "Sample j of a record sits at its stamp + j/500 s. Stamps run −0.95 ppm against the sample count (stamp arithmetic), and the five records after each reset are written out of order; the [[code:timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/connector.py|TimeNet import]] puts them back in sampling order.",
          "offset_s": [
            0.002,
            0.002
          ],
          "status": "measured"
        }
      ],
      "derived": {
        "parameters": [
          "snuadc-gap-stamp-shift",
          "snuadc-reset-loss",
          "snuadc-start-delay"
        ],
        "studies": [
          "airway-heartbeat-pilot",
          "arterial-line-accesses",
          "bis-cardiac-artefact",
          "clock-correction-scan",
          "continuity-gaps-segments",
          "correction-periods",
          "device-correction-schedules",
          "monitor-lines",
          "numeric-clock-check",
          "numeric-derivation-pilot",
          "paced-ecg",
          "pat-offset-census",
          "ppg-art-reference",
          "ppg-frame-clock",
          "ppg-offset-reference",
          "ppg-sawtooth",
          "rare-device-sync",
          "record-messages-and-steps",
          "reset-cluster-order",
          "shared-bursts",
          "snuadc-firmware",
          "snuadc-stamp-sawtooth",
          "solar-snuadc-clock",
          "stamp-arithmetic",
          "step-classification",
          "time-zeros",
          "track-value-resolution",
          "vitaldb-loader"
        ],
        "observations": [
          "arterial-steps-at-line-events",
          "bis-eeg-cardiac-artefact",
          "duplicated-records",
          "eeg-ecg-offset-by-mode",
          "fiducial-quantisation",
          "interleaved-records",
          "loaders-lose-samples",
          "reset-records-out-of-order",
          "reset-signal-lost",
          "reset-zero-sample",
          "small-snuadc-gaps",
          "snuadc-100hz-cases",
          "snuadc-channel-skew",
          "snuadc-no-timestamps",
          "snuadc-one-clock",
          "snuadc-ten-bit-codes",
          "snuadc-value-truncation"
        ],
        "examples": [
          "pat-vs-bp",
          "ppv-by-breath",
          "pulse-arrival-time"
        ]
      }
    },
    "snuadc-pleth": {
      "id": "snuadc-pleth",
      "name": "Plethysmogram (SNUADC)",
      "summary": "The pulse oximeter's plethysmogram, sampled by SNUADC at 500 Hz. It reaches SNUADC through the oximeter's own 30 Hz frame clock and a hardware delay of about 0.4 s.",
      "type": "waveform",
      "source_name": "SNUADC/PLETH",
      "device": "snuadc",
      "stamp_clock": "recorder-pc",
      "sample_clock": "snuadc-sample",
      "rate_hz": 500,
      "timing": [
        {
          "id": "pleth",
          "name": "stamp-model time",
          "default": true,
          "delay_s": [
            0.37,
            0.4
          ],
          "interval_s": [
            0.002,
            0.033
          ],
          "domain": "reference",
          "status": "measured",
          "evidence": "one global hardware delay (pat-offset-census), 0.385 s (0.365-0.405 s with the adopted PAT's 250-290 ms range); 30 Hz frames"
        }
      ],
      "tags": [
        "stub"
      ],
      "chain": [
        {
          "node": "SNUADC sample clock",
          "clock": "snuadc-sample",
          "kind": "clock",
          "effect": "SNUADC samples on its own crystal; the recorder stamps each 0.2 s record from its model of that clock. In step mode the stamps drift (−63 ppm median) and are pulled back about 0.48 s whenever the drift reaches half a second; in frequent-correction mode they stay within about 10 ms. The atlas's stamp model refits the line.",
          "offset_s": [
            0.01,
            0.48
          ],
          "residual_s": [
            0.0003,
            0.003
          ],
          "drift": "−63 ppm median in step mode, pulled back about every 2.08 h",
          "status": "measured",
          "link": "snuadc-step-vs-pc",
          "studies": [
            "snuadc-stamp-sawtooth"
          ]
        },
        {
          "node": "Signal lost at resets",
          "kind": "correction",
          "effect": "At each step-mode reset about 18–20 ms of signal is never sampled, while every record still holds 100 samples. The stamp model adds the loss back at the end of the last record before the new grid; samples inside that record can still be up to about 20 ms late.",
          "offset_s": [
            0.0172,
            0.0182
          ],
          "residual_s": [
            0.0,
            0.02
          ],
          "status": "measured",
          "parameter": "snuadc-reset-loss",
          "studies": [
            "paced-ecg"
          ]
        },
        {
          "node": "Pulse oximeter module",
          "kind": "processing",
          "effect": "The oximeter filters the plethysmogram and plays it out about 0.4 s late (one delay for the release: 0.385 s, with the adopted 0.27 s finger PAT; 0.365–0.405 s over its plausible range), in 33.3 ms frames on its own clock. Its content runs 18–52 ppm against those frames, so one frame repeats every 11–31 min: a sawtooth in the delay.",
          "offset_s": [
            0.365,
            0.405
          ],
          "drift": "18–52 ppm against its frames; one 33.3 ms slip every 11–31 min",
          "status": "measured",
          "clock": "oximeter-frame-clock",
          "studies": [
            "ppg-frame-clock",
            "ppg-sawtooth",
            "ppg-offset-reference"
          ],
          "link": "pleth-content-vs-frames",
          "parameter": "pleth-chain-delay"
        },
        {
          "node": "Frame slips corrected",
          "kind": "correction",
          "effect": "[[software:physiosync]] detects the slips and removes the sawtooth; mid-tooth drift of 2–5 ms remains in trusted stretches.",
          "offset_s": [
            0.0,
            0.0333
          ],
          "residual_s": [
            0.0019,
            0.005
          ],
          "status": "measured",
          "studies": [
            "ppg-sawtooth",
            "ppg-art-reference"
          ]
        },
        {
          "node": "Monitor analogue output",
          "kind": "transport",
          "effect": "The plethysmogram leaves the monitor as an analogue output; any delay there is not measured.",
          "status": "assumed"
        },
        {
          "node": "Stored sample time",
          "kind": "stamping",
          "effect": "Sample j of a record sits at its stamp + j/500 s. Stamps run −0.95 ppm against the sample count (stamp arithmetic), and the five records after each reset are written out of order; the [[code:timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/connector.py|TimeNet import]] puts them back in sampling order.",
          "offset_s": [
            0.002,
            0.002
          ],
          "status": "measured"
        }
      ],
      "derived": {
        "parameters": [
          "adopted-pat",
          "pleth-chain-delay",
          "pleth-sawtooth-rate"
        ],
        "studies": [
          "arterial-line-accesses",
          "clock-correction-scan",
          "continuity-gaps-segments",
          "monitor-lines",
          "pat-offset-census",
          "ppg-art-reference",
          "ppg-frame-clock",
          "ppg-offset-reference",
          "ppg-sawtooth-pauses",
          "ppg-sawtooth",
          "record-messages-and-steps",
          "reset-cluster-order",
          "snuadc-firmware",
          "spo2-latency",
          "step-classification",
          "track-value-resolution",
          "vitaldb-loader"
        ],
        "observations": [
          "art-with-pleth-factors",
          "duplicated-records",
          "fiducial-quantisation",
          "interleaved-records",
          "loaders-lose-samples",
          "oximeter-frame-clock-fingerprint",
          "pleth-30hz-frames",
          "ppg-hardware-delay",
          "ppg-sawtooth-pauses",
          "ppg-sawtooth",
          "reset-records-out-of-order",
          "reset-signal-lost",
          "reset-zero-sample",
          "snuadc-100hz-cases",
          "snuadc-channel-skew",
          "snuadc-no-timestamps",
          "snuadc-one-clock",
          "snuadc-ten-bit-codes",
          "snuadc-value-truncation"
        ],
        "examples": [
          "pat-vs-bp",
          "pulse-arrival-time"
        ]
      }
    },
    "solar8000-art-numerics": {
      "id": "solar8000-art-numerics",
      "name": "Solar8000 ART and CVP numerics",
      "summary": "Arterial and central venous pressure values from the Solar8000 monitor.",
      "type": "numeric",
      "source_name": "Solar8000/ART_MBP",
      "device": "solar8000",
      "stamp_clock": "recorder-pc",
      "unit": "mmHg",
      "timing": [
        {
          "id": "solar-lab",
          "name": "after the per-case delay label",
          "default": true,
          "delay_s": [
            0.055,
            0.4
          ],
          "interval_s": [
            3.0,
            8.0
          ],
          "domain": "solar8000",
          "status": "measured",
          "evidence": "numeric_delay label SE p10-p90: ART 0.06-0.35 s, CVP 0.11-0.40 s (fixed within a case)",
          "corrected_delay_s": [
            0.3,
            4.7
          ],
          "correction": "per-case delay label",
          "corrects": [
            "solar-win"
          ]
        },
        {
          "id": "solar-win",
          "name": "as stamped",
          "delay_s": [
            0.3,
            4.7
          ],
          "interval_s": [
            3.0,
            8.0
          ],
          "domain": "solar8000",
          "status": "measured",
          "evidence": "delay from window end: ART 0.3-2.5 s, CVP 3.1-4.7 s; windows 3-6 s + 2 s poll"
        }
      ],
      "tags": [
        "stub"
      ],
      "delays": [
        {
          "name": "Mean arterial pressure",
          "parameter": "art-mbp-delay",
          "description": "EWMA ¼ per beat; systolic (median over 3 s) and diastolic (EWMA ½) are about 0.5 s shorter."
        },
        {
          "name": "CVP",
          "parameter": "cvp-delay",
          "description": "Waveform mean over 4 s; the slowest Solar8000 numeric."
        }
      ],
      "derived": {
        "parameters": [
          "art-mbp-delay",
          "cvp-delay"
        ],
        "studies": [
          "numeric-delay-variability",
          "numeric-derivation-pilot",
          "solar-snuadc-clock",
          "track-value-resolution"
        ],
        "observations": [
          "numeric-delays-per-case",
          "numerics-not-front-stamped"
        ],
        "examples": [
          "cuff-vs-arterial",
          "hypotension-burden",
          "induction-hypotension",
          "pat-vs-bp"
        ]
      }
    },
    "solar8000-hr": {
      "id": "solar8000-hr",
      "name": "Solar8000 heart rate",
      "summary": "Heart rate from the Solar8000, in one of two delay groups per case.",
      "type": "numeric",
      "source_name": "Solar8000/HR",
      "device": "solar8000",
      "stamp_clock": "recorder-pc",
      "unit": "/min",
      "timing": [
        {
          "id": "hr-lab",
          "name": "after the per-case delay label",
          "default": true,
          "delay_s": [
            0.12,
            0.61
          ],
          "interval_s": [
            6.0,
            8.0
          ],
          "domain": "solar8000",
          "status": "measured",
          "evidence": "numeric_delay label SE p10-p90 0.12-0.61 s (6,222 cases; 795 group-uncertain)",
          "corrected_delay_s": [
            1.05,
            5.25
          ],
          "correction": "per-case delay label (early or late group)",
          "corrects": [
            "hr-early",
            "hr-late"
          ]
        },
        {
          "id": "hr-early",
          "name": "as stamped, early group",
          "delay_s": [
            1.05,
            2.3
          ],
          "interval_s": [
            6.0,
            8.0
          ],
          "domain": "solar8000",
          "status": "measured",
          "evidence": "delay p10-p90 1.05-2.3 s, 1,775 cases; best window 6-8 s"
        },
        {
          "id": "hr-late",
          "name": "as stamped, late group",
          "delay_s": [
            3.6,
            5.25
          ],
          "interval_s": [
            6.0,
            8.0
          ],
          "domain": "solar8000",
          "status": "measured",
          "evidence": "delay p10-p90 3.6-5.25 s, 3,652 cases; one extra 2 s cycle; follows the PC"
        }
      ],
      "chain": [
        {
          "node": "Heart rate algorithm",
          "kind": "window",
          "effect": "The Solar8000 averages heart rate over a window of about 6–8 s of ECG.",
          "offset_s": [
            6.0,
            8.0
          ],
          "status": "measured"
        },
        {
          "node": "Polled by the recorder",
          "kind": "transport",
          "effect": "The value reaches the recorder 1.05–2.3 s after its window ends in the early group of cases, or 3.6–5.25 s in the late group (one extra 2 s poll cycle). A per-case delay label fixes the offset to 0.12–0.61 s (standard error, p10–p90).",
          "offset_s": [
            1.05,
            5.25
          ],
          "residual_s": [
            0.12,
            0.61
          ],
          "status": "measured"
        },
        {
          "node": "Stored stamp",
          "clock": "recorder-pc",
          "kind": "stamping",
          "effect": "Stamped by the recorder on arrival, on a 1 ms grid; the receipt latency is not measured.",
          "offset_s": [
            0.001,
            0.02
          ],
          "status": "estimated"
        }
      ],
      "window": {
        "name": "Heart rate window",
        "parameter": "hr-window",
        "description": "60 / median R–R over about 6 s, truncated to a whole beat per minute."
      },
      "delays": [
        {
          "name": "Early group",
          "parameter": "hr-delay-early",
          "description": "From the end of the window to the stamp, in about a third of cases."
        },
        {
          "name": "Late group",
          "parameter": "hr-delay-late",
          "description": "One 2 s poll later, in about two thirds of cases. The group belongs to the recording PC."
        }
      ],
      "description": "The Solar8000 computes heart rate from its own ECG as 60 / median R–R over about 6 s, truncated, and the recorder polls it every 2 s. The value is stamped on arrival, one or two polls after its window ends depending on the recording PC ([[observation:hr-two-delay-groups]]). Per-case delay labels fix the offset to a few hundred ms.",
      "derived": {
        "parameters": [
          "hr-delay-early",
          "hr-delay-late",
          "hr-window"
        ],
        "studies": [
          "hr-delay-groups",
          "numeric-clock-check",
          "numeric-delay-variability",
          "numeric-derivation-pilot",
          "solar-snuadc-clock",
          "track-value-resolution"
        ],
        "observations": [
          "hr-truncated",
          "hr-two-delay-groups",
          "numeric-delays-per-case",
          "numerics-not-front-stamped"
        ]
      }
    },
    "solar8000-nibp": {
      "id": "solar8000-nibp",
      "name": "Non-invasive blood pressure",
      "summary": "Cuff blood pressure, recorded after each cuff cycle.",
      "type": "numeric",
      "source_name": "Solar8000/NIBP_MBP",
      "device": "solar8000",
      "stamp_clock": "recorder-pc",
      "unit": "mmHg",
      "timing": [
        {
          "id": "nibp",
          "name": "as stamped",
          "default": true,
          "delay_s": [
            20.0,
            40.0
          ],
          "interval_s": [
            30.0,
            40.0
          ],
          "domain": "solar8000",
          "status": "measured",
          "evidence": "result 20-40 s after the cuff measurement"
        }
      ],
      "tags": [
        "stub"
      ],
      "references": [
        "goodwin2021blood"
      ],
      "derived": {
        "studies": [
          "arterial-line-accesses",
          "track-value-resolution"
        ],
        "observations": [
          "cuff-occludes-line"
        ],
        "examples": [
          "cuff-vs-arterial",
          "induction-hypotension"
        ]
      }
    },
    "solar8000-pleth-hr": {
      "id": "solar8000-pleth-hr",
      "name": "Solar8000 pulse rate (PLETH_HR)",
      "summary": "Pulse rate from the oximeter, over about 12 beats.",
      "type": "numeric",
      "source_name": "Solar8000/PLETH_HR",
      "device": "solar8000",
      "stamp_clock": "recorder-pc",
      "unit": "/min",
      "timing": [
        {
          "id": "pleth-hr",
          "name": "as stamped",
          "default": true,
          "delay_s": [
            2.8,
            4.6
          ],
          "interval_s": [
            8.0,
            12.0
          ],
          "domain": "solar8000",
          "status": "measured",
          "evidence": "delay 2.4-4.2 s + 0.4 s PLETH; ~12 beats"
        }
      ],
      "tags": [
        "stub"
      ],
      "derived": {
        "studies": [
          "numeric-derivation-pilot",
          "spo2-latency"
        ],
        "observations": [
          "numerics-not-front-stamped",
          "spo2-freezes"
        ]
      }
    },
    "solar8000-spo2": {
      "id": "solar8000-spo2",
      "name": "SpO2",
      "summary": "Oxygen saturation from the pulse oximeter.",
      "type": "numeric",
      "source_name": "Solar8000/PLETH_SPO2",
      "device": "solar8000",
      "stamp_clock": "recorder-pc",
      "unit": "%",
      "timing": [
        {
          "id": "spo2",
          "name": "as stamped",
          "default": true,
          "delay_s": [
            4.0,
            4.8
          ],
          "interval_s": [
            10.0,
            20.0
          ],
          "domain": "solar8000",
          "status": "measured",
          "evidence": "onset +4 s (+0.4 s PLETH); window 10-20 s estimated, not measured"
        }
      ],
      "tags": [
        "stub"
      ],
      "delays": [
        {
          "name": "Onset",
          "parameter": "spo2-onset",
          "description": "From step events; the window itself is estimated."
        }
      ],
      "derived": {
        "parameters": [
          "spo2-onset"
        ],
        "studies": [
          "spo2-latency",
          "track-value-resolution"
        ],
        "observations": [
          "numerics-not-front-stamped",
          "spo2-freezes"
        ],
        "examples": [
          "gas-gradients-at-the-draw"
        ]
      }
    },
    "tbil": {
      "id": "tbil",
      "name": "Total bilirubin",
      "summary": "Total bilirubin (lab table).",
      "type": "lab",
      "source_name": "tbil",
      "group": "Laboratory/Chemistry",
      "source": "lab_data.csv",
      "unit": "mg/dL",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.1 mg/dL, nearest",
        "step": 0.1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#tbil"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; last digit flat.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/tbil.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 36,088 records, median 0.8 mg/dL; 281 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of tbil."
        }
      ],
      "derived": {}
    },
    "tprot": {
      "id": "tprot",
      "name": "Total protein",
      "summary": "Total protein (lab table).",
      "type": "lab",
      "source_name": "tprot",
      "group": "Laboratory/Chemistry",
      "source": "lab_data.csv",
      "unit": "g/dL",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.1 g/dL, nearest",
        "step": 0.1,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#tprot"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; last digit flat.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/tprot.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 36,034 records, median 6 g/dL; 304 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of tprot."
        }
      ],
      "derived": {}
    },
    "tubesize": {
      "id": "tubesize",
      "name": "Endotracheal tube size",
      "summary": "Endotracheal tube size (clinical table).",
      "type": "clinical",
      "source_name": "tubesize",
      "group": "Clinical record/Airway and lines",
      "source": "clinical_data.csv",
      "unit": "mm",
      "value": {
        "kind": "not_applicable: categorical or identifier"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Discrete category or device size; not a resolution question.",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/tubesize.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 4,919 records in 12 categories.",
          "alt": "Histogram of the recorded values of tubesize."
        }
      ],
      "derived": {}
    },
    "vigilance-co": {
      "id": "vigilance-co",
      "name": "Vigilance continuous cardiac output",
      "summary": "Continuous cardiac output and averaged HR.",
      "type": "numeric",
      "source_name": "Vigilance/CO",
      "device": "vigilance",
      "stamp_clock": "recorder-pc",
      "unit": "L/min",
      "timing": [
        {
          "id": "vigilance",
          "name": "as stamped",
          "default": true,
          "delay_s": [
            1.0,
            175.0
          ],
          "interval_s": [
            600.0,
            2185.0
          ],
          "domain": "vigilance",
          "status": "estimated",
          "evidence": "running median over 10-36 min; 0-175 s after its window ends (52-case pilot, 16 at zero)"
        }
      ],
      "tags": [
        "stub"
      ],
      "derived": {
        "studies": [
          "rare-device-sync",
          "track-value-resolution"
        ]
      }
    },
    "vigileo-sv": {
      "id": "vigileo-sv",
      "name": "Vigileo SV, CO and SVV",
      "summary": "Stroke volume, cardiac output and stroke volume variation.",
      "type": "numeric",
      "source_name": "Vigileo/SV",
      "device": "vigileo",
      "stamp_clock": "recorder-pc",
      "timing": [
        {
          "id": "vigileo",
          "name": "as stamped",
          "default": true,
          "delay_s": [
            2.0,
            25.0
          ],
          "interval_s": [
            20.0,
            20.0
          ],
          "domain": "vigileo",
          "status": "measured",
          "evidence": "SVV vs arterial PPV: aligned in 25 of 343 cases, |offset| 4.7 s median, p95 26 s"
        }
      ],
      "tags": [
        "stub"
      ],
      "derived": {
        "studies": [
          "rare-device-sync",
          "track-value-resolution"
        ]
      }
    },
    "wbc": {
      "id": "wbc",
      "name": "White blood cell count",
      "summary": "White blood cell count (lab table).",
      "type": "lab",
      "source_name": "wbc",
      "group": "Laboratory/Haematology",
      "source": "lab_data.csv",
      "unit": "×1000/mcL",
      "value": {
        "kind": "uniform: single resolution; uniform over the rounding interval",
        "resolution": "0.01 ×1000/mcL, nearest",
        "step": 0.01,
        "anchor": "nearest",
        "anchor_basis": "convention",
        "anchor_note": "Laboratory results rounded to the last reported digit; at most half the last digit.",
        "spread": "vitaldb-resolution/spread.json#wbc"
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "description": "Single resolution; last digit flat.\n\nIts time is the lab system's result time: see [[variable:lab-blood-gas]] and [[variable:lab-central]].",
      "stamp_clock": "hospital-records",
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/wbc.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 43,941 records, median 8.2 ×1000/mcL; 439 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of wbc."
        }
      ],
      "derived": {}
    },
    "weight": {
      "id": "weight",
      "name": "Weight",
      "summary": "Weight (clinical table).",
      "type": "clinical",
      "source_name": "weight",
      "group": "Clinical record/Patient",
      "source": "clinical_data.csv",
      "unit": "kg",
      "value": {
        "kind": "model: fitted rounding model (value kind); posterior over the true value",
        "resolution": "heaped: a mixture of rounding modes (see the value model)",
        "spread": "vitaldb-resolution/spread.json#weight",
        "model": "vitaldb-resolution/vitaldb_resolution/data/models/weight.model.json",
        "status": "measured",
        "studies": [
          "value-model-weight"
        ]
      },
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "figures": [
        {
          "id": "distribution",
          "file": "distributions/weight.png",
          "theme": "distributions of recorded values",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Recorded values in the release: 6,388 records, median 60.5 kg; 63 outside the 0.5–99.5th percentiles are not shown.",
          "alt": "Histogram of the recorded values of weight."
        }
      ],
      "derived": {
        "studies": [
          "value-model-weight"
        ],
        "examples": [
          "oliguria-labels",
          "propofol-against-pump",
          "regression-with-rounding",
          "urine-output-per-kg-per-hour"
        ]
      }
    }
  },
  "links": {
    "bis-vs-pc": {
      "id": "bis-vs-pc",
      "name": "BIS against the recorder PC",
      "summary": "The BIS EEG stamps drift about +25 ppm against the PC (median from the correction steps of 1,939 recordings; a fit over every recording's stamps gives +21 ppm in 4,728) and are pulled forward on a fixed 8,192 s timer of the BIS's own, which does not start with the recording.",
      "clock": "bis-clock",
      "against": "recorder-pc",
      "rate": "bis-rate",
      "correction": {
        "rule": "On a fixed timer: every 8,192 s the run is cut and restarted forward by whatever drift has built up. A correction\nmoves every BIS track together. In all recordings ([[study:device-correction-schedules]]), 95% of corrections\nafter the first are 8,192 ± 20 s apart, and the first comes at any time after the BIS starts: the timer is the\nBIS's own. The rate is relative to the recorder PC, whose own corrections are part of what it measures.\n",
        "period": "bis-period",
        "size": "bis-step"
      },
      "status": "measured",
      "derived": {
        "parameters": [
          "bis-rate",
          "bis-period",
          "bis-step"
        ]
      }
    },
    "bis-vs-snuadc": {
      "id": "bis-vs-snuadc",
      "name": "BIS against SNUADC",
      "summary": "Measured directly from bursts seen in both the ECG and the EEG, independent of the recorder: −72 ppm (median of 191 benchmark cases), and a constant offset of about −0.8 s left after both stamp models. Per case the slope matches the difference of the two drifts against the PC ([[study:shared-bursts]]); the release medians of those drifts (+25 and −63 ppm) differ by 88 ppm, being medians over different sets of recordings.",
      "clock": "bis-clock",
      "against": "snuadc-sample",
      "rate": "bis-snuadc-rate",
      "correction": {
        "rule": "Both devices' own corrections: SNUADC on its drift threshold, BIS on its 8,192 s timer."
      },
      "status": "measured",
      "derived": {
        "parameters": [
          "bis-snuadc-rate",
          "eeg-ecg-offset",
          "stamp-model-error"
        ]
      }
    },
    "cardioq-vs-pc": {
      "id": "cardioq-vs-pc",
      "name": "CardioQ against the recorder PC",
      "summary": "The CardioQ's stamps are re-anchored about every 5 minutes, with jumps of either sign up to ±2 s.",
      "clock": "cardioq-clock",
      "against": "recorder-pc",
      "correction": {
        "rule": "Irregular re-anchoring, about 40 times per case; drift within a stretch is taken as zero.",
        "period": "cardioq-reanchor"
      },
      "status": "measured",
      "derived": {
        "parameters": [
          "cardioq-reanchor"
        ]
      }
    },
    "cardioq-vs-snuadc": {
      "id": "cardioq-vs-snuadc",
      "name": "CardioQ against SNUADC",
      "summary": "Beats matched between the CardioQ's arterial pressure and SNUADC's put the CardioQ on the SNUADC timeline within each re-anchored stretch (matched-beat MAD 0.24 s).",
      "clock": "cardioq-clock",
      "against": "snuadc-sample",
      "correction": {
        "rule": "One offset per stretch between CardioQ re-anchors, from beat matching.",
        "size": "cardioq-snuadc-lag"
      },
      "status": "measured",
      "derived": {
        "parameters": [
          "cardioq-snuadc-lag"
        ]
      }
    },
    "hospital-records-vs-pc": {
      "id": "hospital-records-vs-pc",
      "name": "Hospital records clock against the recorder PC",
      "summary": "The offset between the anaesthesia record and lab systems and the recorder PC cannot be measured in the release: it is confounded with documentation lag. Entries rarely precede the device events they document.",
      "clock": "hospital-records",
      "against": "recorder-pc",
      "correction": {
        "rule": "Not observable. Both are assumed to keep hospital time synchronised to a common server."
      },
      "status": "assumed",
      "derived": {}
    },
    "monitor-output-vs-snuadc": {
      "id": "monitor-output-vs-snuadc",
      "name": "Monitor output clock against SNUADC",
      "summary": "The 240 Hz line in the monitor's analogue outputs runs −25 to −29 ppm against the SNUADC sample clock (6 files).",
      "clock": "monitor-output-clock",
      "against": "snuadc-sample",
      "rate": "monitor-240hz-rate",
      "status": "estimated",
      "derived": {}
    },
    "oximeter-frames-vs-snuadc": {
      "id": "oximeter-frames-vs-snuadc",
      "name": "Oximeter frames against SNUADC",
      "summary": "The oximeter's 33.3 ms frame period is fixed per setup against the SNUADC sample clock, +7 to +92 ppm across 42 PC keys, and keeps its phase through the sawtooth slips.",
      "clock": "oximeter-frame-clock",
      "against": "snuadc-sample",
      "rate": "pleth-frame-clock-rate",
      "status": "measured",
      "derived": {
        "parameters": [
          "pleth-frame-clock-rate"
        ]
      }
    },
    "pacemaker-vs-snuadc": {
      "id": "pacemaker-vs-snuadc",
      "name": "Pacemaker against SNUADC",
      "summary": "At a fixed pacing rate the pacing interval, measured on the SNUADC sample count, agrees between stretches to 0.05–0.1 ms: the SNUADC sample stream is continuous apart from the resets and gaps.",
      "clock": "pacemaker",
      "against": "snuadc-sample",
      "correction": {
        "rule": "None; the pacemaker is never corrected. A phase step in its train marks a break in the SNUADC timeline."
      },
      "status": "measured",
      "derived": {}
    },
    "pleth-content-vs-frames": {
      "id": "pleth-content-vs-frames",
      "name": "PPG content against the oximeter frames (the sawtooth)",
      "summary": "The PPG content runs 18–52 ppm against the oximeter's frames, fixed per setup. When a whole frame of lag has built up, one frame is repeated: the delay steps up by 33.3 ms, then creeps down again.",
      "clock": "oximeter-frame-clock",
      "against": "snuadc-sample",
      "rate": "pleth-sawtooth-rate",
      "correction": {
        "rule": "One repeated 33.3 ms frame whenever a frame of lag has built up (a slip). [[software:physiosync]] detects the slips in the\nbeat-matched ECG→PPG delay and removes the sawtooth tooth by tooth.\n",
        "size": "pleth-slip-step"
      },
      "status": "measured",
      "derived": {
        "parameters": [
          "pleth-sawtooth-rate",
          "pleth-slip-step"
        ]
      }
    },
    "primus-vs-pc": {
      "id": "primus-vs-pc",
      "name": "Primus against the recorder PC",
      "summary": "The Primus stamps drift a median +104 ppm against the recorder PC (measured by breath matching through the SNUADC stamp model). Its small corrections every 7,230 s, each about 21 s after a pause in the AWP waveform, do not keep up with that drift.",
      "clock": "primus-clock",
      "against": "recorder-pc",
      "rate": "primus-drift-breath",
      "correction": {
        "rule": "Small steps, whole 16 ms samples in either direction (median −16 ms; p10–p90 −144 to +144 ms), every 7,230 s or twice\nthat, often two at once; the first at any time after the Primus starts. The AWP waveform stops for about 21 s\njust before each correction and that AWP is lost ([[observation:primus-awp-pauses]]); CO2 carries on. Measured in\n4,529 recordings ([[study:device-correction-schedules]]). These steps imply only 1–11 ppm\n([[parameter:primus-drift-implied]]), far less than the drift: the stamps also step at the AWP pauses, by amounts unrelated to the drift built up: a median −1 ms\n(p10–p90 −164 to +187 ms) where CO2 carries on, up to seconds where CO2 stops too ([[study:primus-pause-steps]]), so over a recording\nthe Primus wanders from the PC by a median 1.5 s.\n",
        "period": "primus-period",
        "size": "primus-step"
      },
      "status": "measured",
      "derived": {
        "parameters": [
          "primus-drift-assumed",
          "primus-drift-breath",
          "primus-drift-implied",
          "primus-period",
          "primus-step"
        ]
      }
    },
    "recorder-pc-vs-utc": {
      "id": "recorder-pc-vs-utc",
      "name": "Recorder PC against UTC",
      "summary": "The recorder PC keeps Windows time, synchronised to the hospital's time server every 8–24 hours. Its offset from UTC is assumed, not observed; no step of 0.5 s or more appears anywhere in the release.",
      "clock": "recorder-pc",
      "against": "utc",
      "rate": "pc-utc-drift",
      "correction": {
        "rule": "Windows time synchronisation (NTP to the hospital time server) every 8–24 h is assumed. Whatever correction\nit makes is smaller than 0.5 s: no simultaneous jump of every device of that size was found in any file\n([[parameter:pc-step-bound]]).\n",
        "period": "pc-sync-period"
      },
      "status": "assumed",
      "derived": {
        "parameters": [
          "pc-step-bound",
          "pc-sync-period",
          "pc-utc-drift",
          "pc-utc-offset"
        ]
      }
    },
    "snuadc-frequent-vs-pc": {
      "id": "snuadc-frequent-vs-pc",
      "name": "SNUADC (frequent-correction mode) against the recorder PC",
      "summary": "In frequent-correction mode the SNUADC stamps follow the PC, with small forward steps; the −0.95 ppm they show against the sample count is stamp arithmetic.",
      "clock": "snuadc-sample",
      "against": "recorder-pc",
      "modes": [
        "frequent"
      ],
      "rate": "snuadc-stamp-arithmetic",
      "correction": {
        "rule": "Small forward steps of +5 to +15 ms, about one an hour; never the 0.48 s pull-back of step mode.",
        "size": "snuadc-frequent-steps"
      },
      "status": "measured",
      "derived": {
        "parameters": [
          "snuadc-frequent-steps",
          "snuadc-stamp-arithmetic"
        ]
      }
    },
    "snuadc-step-vs-pc": {
      "id": "snuadc-step-vs-pc",
      "name": "SNUADC (step mode) against the recorder PC",
      "summary": "In step mode the SNUADC stamps drift by tens of ppm and are pulled back by about half a second whenever the drift reaches that size, about every two hours.",
      "clock": "snuadc-sample",
      "against": "recorder-pc",
      "modes": [
        "step"
      ],
      "rate": "snuadc-step-rate",
      "correction": {
        "rule": "Triggered by drift, not by a timer: once the stamps are about 0.5 s ahead, the recorder pulls the track back.\nThe stamps show only −0.480 s of the true correction of about 0.498 s, because about 18 ms of signal is never\nsampled ([[parameter:snuadc-reset-loss]]). In all recordings ([[study:device-correction-schedules]]): 3,417\nresets in 2,540 recordings, a median 2.08 h apart; the drift count usually starts with the recording (the first\nreset falls at 0.99 × the time 0.49 s of drift takes, interquartile 0.87–1.01), though not reliably enough to\ntime drift from a single reset. Rates are relative to the recorder PC.\n",
        "period": "snuadc-step-period",
        "size": "snuadc-step-correction-size"
      },
      "status": "measured",
      "derived": {
        "parameters": [
          "snuadc-step-rate",
          "snuadc-reset-loss",
          "snuadc-step-correction-size",
          "snuadc-step-period"
        ]
      }
    },
    "solar8000-vs-snuadc": {
      "id": "solar8000-vs-snuadc",
      "name": "Solar8000 numerics against SNUADC",
      "summary": "The Solar8000's numbers carry no clock of their own in the release; their delay behind the SNUADC waveforms they summarise is fixed within a case, which bounds any drift of the monitor's timing.",
      "clock": "solar8000-clock",
      "against": "snuadc-sample",
      "rate": "solar-delay-drift",
      "correction": {
        "rule": "None needed within a case; one delay per case (the numeric_delay label) puts each numeric on the SNUADC timeline."
      },
      "status": "measured",
      "derived": {
        "parameters": [
          "solar-delay-drift"
        ]
      }
    }
  },
  "parameters": {
    "adopted-pat": {
      "id": "adopted-pat",
      "name": "Adopted mean pulse arrival time",
      "quantity": "R peak to finger PPG foot in anaesthetised adults, used to remove the PPG hardware delay.",
      "value": 270,
      "unit": "ms",
      "display": "270 ms (plausible range 250–290 ms)",
      "status": "assumed",
      "study": "ppg-offset-reference",
      "about": [
        "snuadc-pleth"
      ],
      "derived": {}
    },
    "art-mbp-delay": {
      "id": "art-mbp-delay",
      "name": "Solar8000 ART mean delay",
      "quantity": "Delay from the end of the window to the stamp of the Solar8000 mean arterial pressure (EWMA ¼ per beat).",
      "value": 1.7,
      "unit": "s",
      "spread": {
        "p10": 1.0,
        "p90": 2.5
      },
      "status": "measured",
      "study": "numeric-delay-variability",
      "about": [
        "solar8000-art-numerics"
      ],
      "derived": {}
    },
    "bis-index-delay": {
      "id": "bis-index-delay",
      "name": "BIS index delay",
      "quantity": "Delay from the end of the EEG window to the start of the index's trailing average.",
      "value": 3,
      "unit": "s",
      "spread": {
        "p10": 3,
        "p90": 4
      },
      "status": "measured",
      "study": "bis-index-delay",
      "about": [
        "bis-index"
      ],
      "derived": {}
    },
    "bis-index-window": {
      "id": "bis-index-window",
      "name": "BIS index averaging window",
      "quantity": "Length of the trailing average that makes up the fast part of the BIS index's response.",
      "display": "16 s in 48% of good fits (the 15 s setting); 32–61 s in 40%; half the weight older than 30 s",
      "status": "measured",
      "study": "bis-index-delay",
      "about": [
        "bis-index"
      ],
      "derived": {}
    },
    "bis-period": {
      "id": "bis-period",
      "name": "BIS correction interval",
      "quantity": "Time between BIS corrections.",
      "value": 8192,
      "unit": "s",
      "display": "8,192 s (2 h 16.5 min), ± 20 s in 95% of intervals",
      "status": "measured",
      "study": "device-correction-schedules",
      "about": [
        "bis-vs-pc"
      ],
      "date": "2026-10-19",
      "notes": "The whole release: 95% of corrections after the first are 8,192 ± 20 s apart; the first comes at any time after the BIS starts. The pilot ([[study:correction-periods]]) found the same in 95% of 266 intervals.",
      "derived": {}
    },
    "bis-rate": {
      "id": "bis-rate",
      "setup_key": "PC key: SNUADC · Solar8000 · Primus · BIS serial ports",
      "by_setup": [
        {
          "setup": "COM4 · COM6 · COM5 · COM8",
          "value": 44.1,
          "q1": 42.7,
          "q3": 46.9,
          "n": 186
        },
        {
          "setup": "COM4 · COM12 · COM11 · COM14",
          "value": 50.5,
          "q1": 49.7,
          "q3": 51.0,
          "n": 178
        },
        {
          "setup": "COM19 · COM26 · COM27 · COM29",
          "value": 32.3,
          "q1": 30.6,
          "q3": 38.6,
          "n": 93
        },
        {
          "setup": "COM12 · COM4 · COM5 · COM7",
          "value": 21.5,
          "q1": 20.5,
          "q3": 22.6,
          "n": 75
        },
        {
          "setup": "COM4 · COM9 · COM10 · COM20",
          "value": 18.6,
          "q1": 17.9,
          "q3": 19.2,
          "n": 64
        },
        {
          "setup": "COM3 · COM4 · COM5 · COM7",
          "value": 25.1,
          "q1": 23.3,
          "q3": 32.2,
          "n": 60
        },
        {
          "setup": "COM13 · COM26 · COM27 · COM19",
          "value": 17.4,
          "q1": 16.7,
          "q3": 18.1,
          "n": 56
        },
        {
          "setup": "COM25 · COM26 · COM27 · COM29",
          "value": 30.9,
          "q1": 29.2,
          "q3": 32.4,
          "n": 56
        },
        {
          "setup": "COM4 · COM5 · COM6 · COM3",
          "value": 49.4,
          "q1": 48.2,
          "q3": 49.8,
          "n": 53
        },
        {
          "setup": "COM7 · COM4 · COM5 · COM9",
          "value": 18.9,
          "q1": 17.8,
          "q3": 19.7,
          "n": 51
        },
        {
          "setup": "COM18 · COM13 · COM14 · COM6",
          "value": 18.2,
          "q1": 17.5,
          "q3": 18.8,
          "n": 48
        },
        {
          "setup": "COM4 · COM5 · COM6 · COM8",
          "value": 54.7,
          "q1": 52.6,
          "q3": 55.2,
          "n": 44
        },
        {
          "setup": "COM4 · COM9 · COM10 · COM12",
          "value": 30.5,
          "q1": 29.9,
          "q3": 31.3,
          "n": 44
        },
        {
          "setup": "COM13 · COM14 · COM15 · COM19",
          "value": 18.4,
          "q1": 17.8,
          "q3": 19.4,
          "n": 39
        },
        {
          "setup": "COM4 · COM5 · COM6 · COM12",
          "value": 28.2,
          "q1": 27.8,
          "q3": 30.0,
          "n": 39
        },
        {
          "setup": "COM14 · COM10 · COM11 · COM18",
          "value": 25.6,
          "q1": 24.8,
          "q3": 26.5,
          "n": 38
        },
        {
          "setup": "COM45 · COM42 · COM43 · COM39",
          "value": 18.8,
          "q1": 18.3,
          "q3": 20.1,
          "n": 37
        },
        {
          "setup": "COM14 · COM4 · COM5 · COM7",
          "value": 26.3,
          "q1": 25.7,
          "q3": 26.8,
          "n": 36
        },
        {
          "setup": "COM15 · COM13 · COM14 · COM22",
          "value": 19.9,
          "q1": 18.9,
          "q3": 21.1,
          "n": 31
        },
        {
          "setup": "COM4 · COM11 · COM12 · COM8",
          "value": 23.6,
          "q1": 22.8,
          "q3": 24.5,
          "n": 30
        },
        {
          "setup": "COM4 · COM5 · COM6 · COM20",
          "value": 17.2,
          "q1": 16.1,
          "q3": 17.8,
          "n": 27
        },
        {
          "setup": "COM8 · COM42 · COM43 · COM39",
          "value": 18.7,
          "q1": 18.1,
          "q3": 19.8,
          "n": 26
        },
        {
          "setup": "COM4 · COM26 · COM27 · COM29",
          "value": 30.8,
          "q1": 30.4,
          "q3": 31.7,
          "n": 25
        },
        {
          "setup": "COM15 · COM25 · COM26 · COM30",
          "value": 24.4,
          "q1": 23.9,
          "q3": 24.8,
          "n": 23
        },
        {
          "setup": "COM15 · COM26 · COM27 · COM29",
          "value": 29.9,
          "q1": 24.3,
          "q3": 30.4,
          "n": 23
        },
        {
          "setup": "COM13 · COM4 · COM5 · COM23",
          "value": 19.3,
          "q1": 18.5,
          "q3": 20.7,
          "n": 20
        },
        {
          "setup": "COM8 · COM14 · COM15 · COM4",
          "value": 21.0,
          "q1": 19.6,
          "q3": 22.6,
          "n": 20
        },
        {
          "setup": "COM9 · COM15 · COM16 · COM20",
          "value": 26.4,
          "q1": 26.1,
          "q3": 27.2,
          "n": 20
        },
        {
          "setup": "COM13 · COM4 · COM5 · COM9",
          "value": 19.5,
          "q1": 19.1,
          "q3": 20.6,
          "n": 18
        },
        {
          "setup": "COM14 · COM21 · COM22 · COM18",
          "value": 24.7,
          "q1": 24.3,
          "q3": 25.1,
          "n": 18
        },
        {
          "setup": "COM31 · COM28 · COM29 · COM34",
          "value": 18.2,
          "q1": 17.6,
          "q3": 19.3,
          "n": 18
        },
        {
          "setup": "COM9 · COM10 · COM11 · COM16",
          "value": 17.8,
          "q1": 17.0,
          "q3": 18.6,
          "n": 18
        },
        {
          "setup": "COM3 · COM15 · COM16 · COM20",
          "value": 22.9,
          "q1": 22.3,
          "q3": 23.7,
          "n": 16
        },
        {
          "setup": "COM4 · COM11 · COM12 · COM3",
          "value": 49.1,
          "q1": 48.1,
          "q3": 50.0,
          "n": 14
        },
        {
          "setup": "COM15 · COM27 · COM26 · COM29",
          "value": 28.6,
          "q1": 26.4,
          "q3": 29.3,
          "n": 9
        },
        {
          "setup": "COM32 · COM28 · COM27 · COM30",
          "value": 42.6,
          "q1": 42.5,
          "q3": 42.6,
          "n": 9
        },
        {
          "setup": "COM4 · COM13 · COM14 · COM6",
          "value": 18.8,
          "q1": 18.3,
          "q3": 19.8,
          "n": 9
        }
      ],
      "name": "BIS drift against the recorder PC",
      "quantity": "Drift of the BIS EEG stamps against the recorder PC, per case, from whole teeth.",
      "value": 25,
      "unit": "ppm",
      "spread": {
        "p10": 12,
        "p90": 50,
        "text": "5th–95th 7.3–51.1 ppm; census fit of 4,728 recordings median 21.1 ppm"
      },
      "status": "measured",
      "study": "snuadc-stamp-sawtooth",
      "about": [
        "bis-vs-pc"
      ],
      "headline": true,
      "date": "2026-10-19",
      "notes": "Full release, replacing the pilot value of 23 ppm (p10–p90 17–47). From the correction steps of all recordings ([[study:device-correction-schedules]], 19 October): median 24.8 ppm (5th–95th 7.3–51.1) in 1,939 recordings, identical to the clock-correction scan where both exist; the full census fit gives 4,728 recordings, median 21.1 ppm (5th–95th 2.1–52.8). Relative to the recorder PC.",
      "derived": {}
    },
    "bis-snuadc-rate": {
      "id": "bis-snuadc-rate",
      "name": "BIS drift against SNUADC",
      "quantity": "Slope of the ECG–EEG burst-offset ramps, independent of the recorder.",
      "value": -72,
      "unit": "ppm",
      "spread": {
        "q1": -82,
        "q3": -54
      },
      "status": "measured",
      "study": "shared-bursts",
      "about": [
        "bis-vs-snuadc"
      ],
      "derived": {}
    },
    "bis-step": {
      "id": "bis-step",
      "name": "BIS correction size",
      "quantity": "Size of each BIS correction (forward).",
      "value": 0.17,
      "unit": "s",
      "display": "0.17 s median",
      "status": "measured",
      "study": "device-correction-schedules",
      "about": [
        "bis-vs-pc"
      ],
      "date": "2026-10-19",
      "notes": "The whole release; it is whatever drift built up over the 8,192 s since the previous correction. The pilot ([[study:correction-periods]]) gave 0.155 s median (0.083 to 0.289 s) in 266 corrections.",
      "derived": {}
    },
    "cardioq-reanchor": {
      "id": "cardioq-reanchor",
      "name": "CardioQ re-anchoring interval",
      "quantity": "Length of the stretches between CardioQ re-anchors.",
      "display": "at most about 300 s; about 40 re-anchors per case",
      "status": "measured",
      "about": [
        "cardioq-vs-pc"
      ],
      "derived": {}
    },
    "cardioq-snuadc-lag": {
      "id": "cardioq-snuadc-lag",
      "name": "CardioQ stamps behind SNUADC",
      "quantity": "Lag of the CardioQ arterial pressure stamps behind SNUADC arterial pressure, per stretch.",
      "display": "0.4–4.4 s (p5–p95); matched-beat MAD 0.24 s (24 cases)",
      "status": "measured",
      "study": "rare-device-sync",
      "about": [
        "cardioq-vs-snuadc"
      ],
      "derived": {}
    },
    "cvp-delay": {
      "id": "cvp-delay",
      "name": "Solar8000 CVP delay",
      "quantity": "Delay from the end of the 4 s waveform-mean window to the stamp of the Solar8000 CVP.",
      "value": 3.8,
      "unit": "s",
      "spread": {
        "p10": 3.1,
        "p90": 4.7
      },
      "status": "measured",
      "study": "numeric-delay-variability",
      "about": [
        "solar8000-art-numerics"
      ],
      "derived": {}
    },
    "eeg-ecg-offset": {
      "id": "eeg-ecg-offset",
      "name": "EEG–ECG constant after the stamp model",
      "quantity": "EEG minus ECG stamp for the same burst, left after the stamp model, per SNUADC mode.",
      "value": -0.804,
      "unit": "s",
      "display": "−804 ms in step mode (1,526 recordings); −660 ms in frequent-correction mode (78)",
      "status": "measured",
      "study": "snuadc-stamp-sawtooth",
      "about": [
        "bis-vs-snuadc"
      ],
      "date": "2026-10-19",
      "notes": "The whole release, stamp model with the rules of model3 (interquartile range −833 to −776 ms in step mode). The pilot gave −803 ms (117 cases) and −690 ms (29). Recordings with no SNUADC correction sit at −766 ms (1,099).",
      "derived": {}
    },
    "emr-rounding": {
      "id": "emr-rounding",
      "name": "EMR 5-minute entries, rounding",
      "quantity": "Where the true event lies for an anaesthesia record time t entered on a 5-minute mark.",
      "display": "(t − 300 s, t]; estimate t − 150 s (SD 87 s); observed shift against minute entries −89 to −293 s",
      "status": "measured",
      "study": "emr-rounding",
      "about": [
        "emr-events"
      ],
      "derived": {}
    },
    "etco2-delay": {
      "id": "etco2-delay",
      "name": "Primus ETCO2 delay",
      "quantity": "Delay from the end of the breath's CO2 plateau to the stamp of the Primus ETCO2.",
      "value": 1.55,
      "unit": "s",
      "spread": {
        "p10": 0.75,
        "p90": 3.4
      },
      "status": "measured",
      "study": "numeric-delay-variability",
      "about": [
        "primus-numerics"
      ],
      "derived": {}
    },
    "hr-delay-early": {
      "id": "hr-delay-early",
      "name": "Solar8000 HR delay, early group",
      "quantity": "Delay from the end of the HR window to its stamp, in the early group of cases.",
      "value": 1.92,
      "unit": "s",
      "spread": {
        "sd": 0.51,
        "text": "36% of classified cases"
      },
      "status": "measured",
      "study": "hr-delay-groups",
      "about": [
        "solar8000-hr"
      ],
      "derived": {}
    },
    "hr-delay-late": {
      "id": "hr-delay-late",
      "name": "Solar8000 HR delay, late group",
      "quantity": "Delay from the end of the HR window to its stamp, in the late group of cases.",
      "value": 4.2,
      "unit": "s",
      "spread": {
        "sd": 0.65,
        "text": "64% of classified cases"
      },
      "status": "measured",
      "study": "hr-delay-groups",
      "about": [
        "solar8000-hr"
      ],
      "headline": true,
      "derived": {}
    },
    "hr-window": {
      "id": "hr-window",
      "name": "Solar8000 HR window",
      "quantity": "Span of ECG the Solar8000 heart rate summarises (60 / median R–R, truncated).",
      "display": "about 6 s (best 6–8 s in both delay groups)",
      "status": "measured",
      "study": "numeric-derivation-pilot",
      "about": [
        "solar8000-hr"
      ],
      "derived": {}
    },
    "lab-bg-lag": {
      "id": "lab-bg-lag",
      "name": "Blood gas timestamp after the draw",
      "quantity": "Time from the start of the arterial line access (the draw) to the blood gas lab timestamp.",
      "value": 7.2,
      "unit": "min",
      "spread": {
        "p10": 3.2,
        "p90": 12.2,
        "text": "two modes, 2–6 and 8–12 min"
      },
      "status": "measured",
      "study": "lab-timestamp-meaning",
      "about": [
        "lab-blood-gas"
      ],
      "derived": {}
    },
    "lab-central-lag": {
      "id": "lab-central-lag",
      "name": "Central lab timestamp after the draw",
      "quantity": "Time from the draw to the central-lab specimen timestamp.",
      "display": "about 8–24 min (median about 14; weak, mostly chance matches)",
      "status": "estimated",
      "study": "lab-timestamp-meaning",
      "about": [
        "lab-central"
      ],
      "derived": {}
    },
    "line-access-rate": {
      "id": "line-access-rate",
      "name": "Arterial line access rate",
      "quantity": "Accesses to the arterial line (flush, zero, draw) found in the pressure waveform.",
      "display": "1.1 per hour (8,245 in 7,507 h); occlusions 0.4 per hour",
      "status": "measured",
      "study": "arterial-line-accesses",
      "about": [
        "snuadc-art"
      ],
      "derived": {}
    },
    "monitor-240hz-rate": {
      "id": "monitor-240hz-rate",
      "name": "Monitor 240 Hz line against SNUADC",
      "quantity": "Frequency of the 240 Hz line in ART and PLETH relative to the SNUADC sample clock.",
      "display": "−25.4 to −28.7 ppm (6 cases)",
      "status": "measured",
      "study": "monitor-lines",
      "about": [
        "monitor-output-clock"
      ],
      "derived": {}
    },
    "pacemaker-interval-agreement": {
      "id": "pacemaker-interval-agreement",
      "name": "Pacing interval agreement between stretches",
      "quantity": "How closely the pacing interval, measured on the SNUADC sample count, agrees between stretches of a case.",
      "display": "0.05–0.1 ms",
      "status": "measured",
      "study": "paced-ecg",
      "about": [
        "pacemaker",
        "snuadc-sample"
      ],
      "date": "2026-10-17",
      "derived": {}
    },
    "pc-step-bound": {
      "id": "pc-step-bound",
      "name": "Largest recorder PC clock step",
      "quantity": "The largest step of the recorder PC clock, seen as all devices' stamps jumping together.",
      "display": "no step of 0.5 s or more in 6,388 files (19,006 h)",
      "status": "measured",
      "study": "recorder-pc-steps",
      "about": [
        "recorder-pc-vs-utc",
        "recorder-pc"
      ],
      "date": "2026-10-17",
      "derived": {}
    },
    "pc-sync-period": {
      "id": "pc-sync-period",
      "name": "Recorder PC synchronisation interval",
      "quantity": "Time between Windows time synchronisations of the recorder PC.",
      "display": "8–24 h (assumed)",
      "status": "assumed",
      "about": [
        "recorder-pc-vs-utc"
      ],
      "derived": {}
    },
    "pc-utc-drift": {
      "id": "pc-utc-drift",
      "name": "Recorder PC drift against UTC",
      "quantity": "Drift of the recorder PC clock between time synchronisations.",
      "display": "0.1–10 ppm (assumed)",
      "status": "assumed",
      "about": [
        "recorder-pc-vs-utc"
      ],
      "notes": "A typical range for PC crystals; not observable in the release.",
      "derived": {}
    },
    "pc-utc-offset": {
      "id": "pc-utc-offset",
      "name": "Recorder PC offset from UTC",
      "quantity": "How far the recorder PC's time may sit from UTC at any moment.",
      "value": 0.003,
      "unit": "s",
      "display": "3 ms to 0.9 s (assumed)",
      "status": "assumed",
      "about": [
        "recorder-pc-vs-utc"
      ],
      "derived": {}
    },
    "pip-delay": {
      "id": "pip-delay",
      "name": "Primus PIP delay",
      "quantity": "Delay from the end of inspiration to the stamp of the Primus peak pressure.",
      "value": 4.6,
      "unit": "s",
      "spread": {
        "p10": 3.65,
        "p90": 6.5
      },
      "status": "measured",
      "study": "numeric-delay-variability",
      "about": [
        "primus-numerics"
      ],
      "derived": {}
    },
    "pleth-chain-delay": {
      "id": "pleth-chain-delay",
      "name": "PPG hardware delay",
      "quantity": "Delay of the PLETH behind the finger, one value for the release (stored ECG-to-PLETH delay minus the adopted physiological PAT).",
      "display": "385 ms, one global value (stored R-to-foot delay 655 ms minus an adopted 270 ms); setups differ by under ±15 ms",
      "status": "measured",
      "study": "pat-offset-census",
      "about": [
        "snuadc-pleth"
      ],
      "derived": {}
    },
    "pleth-frame-clock-rate": {
      "id": "pleth-frame-clock-rate",
      "setup_key": "PC key: SNUADC · Solar8000 · Primus · BIS serial ports",
      "by_setup": [
        {
          "setup": "COM4 · COM5 · COM6 · COM3",
          "value": 37.0,
          "q1": 35.8,
          "q3": 38.0,
          "n": 20
        },
        {
          "setup": "COM45 · COM42 · COM43 · COM39",
          "value": 68.0,
          "q1": 68.0,
          "q3": 68.0,
          "n": 19
        },
        {
          "setup": "COM7 · COM4 · COM5 · COM9",
          "value": 74.0,
          "q1": 73.0,
          "q3": 74.0,
          "n": 19
        },
        {
          "setup": "COM8 · COM42 · COM43 · COM39",
          "value": 67.0,
          "q1": 67.0,
          "q3": 68.0,
          "n": 19
        },
        {
          "setup": "COM15 · COM25 · COM26 · COM30",
          "value": 9.5,
          "q1": 8.0,
          "q3": 65.0,
          "n": 18
        },
        {
          "setup": "COM25 · COM26 · COM27 · COM29",
          "value": 78.0,
          "q1": 77.0,
          "q3": 78.8,
          "n": 18
        },
        {
          "setup": "COM32 · COM28 · COM27 · COM30",
          "value": 50.0,
          "q1": 50.0,
          "q3": 50.0,
          "n": 18
        },
        {
          "setup": "COM13 · COM4 · COM5 · COM9",
          "value": 73.0,
          "q1": 72.0,
          "q3": 74.0,
          "n": 17
        },
        {
          "setup": "COM14 · COM21 · COM22 · COM18",
          "value": 7.0,
          "q1": 7.0,
          "q3": 8.0,
          "n": 17
        },
        {
          "setup": "COM3 · COM4 · COM5 · COM7",
          "value": 23.0,
          "q1": 23.0,
          "q3": 24.0,
          "n": 17
        },
        {
          "setup": "COM4 · COM12 · COM11 · COM14",
          "value": 50.0,
          "q1": 49.0,
          "q3": 50.0,
          "n": 17
        },
        {
          "setup": "COM4 · COM26 · COM27 · COM29",
          "value": 84.0,
          "q1": 83.0,
          "q3": 84.0,
          "n": 17
        },
        {
          "setup": "COM8 · COM14 · COM15 · COM4",
          "value": 67.0,
          "q1": 66.0,
          "q3": 68.0,
          "n": 17
        },
        {
          "setup": "COM13 · COM26 · COM27 · COM19",
          "value": 92.0,
          "q1": 91.0,
          "q3": 93.0,
          "n": 16
        },
        {
          "setup": "COM13 · COM4 · COM5 · COM23",
          "value": 73.0,
          "q1": 72.0,
          "q3": 73.0,
          "n": 16
        },
        {
          "setup": "COM14 · COM10 · COM11 · COM18",
          "value": 8.0,
          "q1": 7.0,
          "q3": 8.0,
          "n": 16
        },
        {
          "setup": "COM3 · COM15 · COM16 · COM20",
          "value": 68.0,
          "q1": 67.0,
          "q3": 68.2,
          "n": 16
        },
        {
          "setup": "COM4 · COM5 · COM6 · COM12",
          "value": 84.0,
          "q1": 83.0,
          "q3": 84.2,
          "n": 16
        },
        {
          "setup": "COM4 · COM5 · COM6 · COM8",
          "value": 37.0,
          "q1": 36.0,
          "q3": 37.2,
          "n": 16
        },
        {
          "setup": "COM4 · COM9 · COM10 · COM12",
          "value": 84.0,
          "q1": 83.0,
          "q3": 85.0,
          "n": 16
        },
        {
          "setup": "COM14 · COM4 · COM5 · COM7",
          "value": 8.0,
          "q1": 7.0,
          "q3": 8.5,
          "n": 15
        },
        {
          "setup": "COM31 · COM28 · COM29 · COM34",
          "value": 91.0,
          "q1": 90.0,
          "q3": 91.0,
          "n": 15
        },
        {
          "setup": "COM4 · COM11 · COM12 · COM8",
          "value": 67.0,
          "q1": 67.0,
          "q3": 68.0,
          "n": 15
        },
        {
          "setup": "COM4 · COM6 · COM5 · COM8",
          "value": 40.0,
          "q1": 40.0,
          "q3": 43.0,
          "n": 15
        },
        {
          "setup": "COM15 · COM26 · COM27 · COM29",
          "value": 71.5,
          "q1": 71.0,
          "q3": 72.0,
          "n": 14
        },
        {
          "setup": "COM18 · COM13 · COM14 · COM6",
          "value": 84.0,
          "q1": 84.0,
          "q3": 84.8,
          "n": 14
        },
        {
          "setup": "COM4 · COM5 · COM6 · COM20",
          "value": 92.0,
          "q1": 91.2,
          "q3": 93.0,
          "n": 14
        },
        {
          "setup": "COM9 · COM15 · COM16 · COM20",
          "value": 84.0,
          "q1": 84.0,
          "q3": 85.0,
          "n": 14
        },
        {
          "setup": "COM13 · COM14 · COM15 · COM19",
          "value": 91.0,
          "q1": 91.0,
          "q3": 92.0,
          "n": 13
        },
        {
          "setup": "COM4 · COM11 · COM12 · COM3",
          "value": 38.0,
          "q1": 37.0,
          "q3": 40.0,
          "n": 13
        },
        {
          "setup": "COM14 · COM16 · COM5 · COM7",
          "value": 8.0,
          "q1": 7.0,
          "q3": 8.0,
          "n": 12
        },
        {
          "setup": "COM19 · COM26 · COM27 · COM29",
          "value": 72.0,
          "q1": 71.8,
          "q3": 75.0,
          "n": 12
        },
        {
          "setup": "COM15 · COM27 · COM26 · COM29",
          "value": 72.0,
          "q1": 71.0,
          "q3": 72.0,
          "n": 11
        },
        {
          "setup": "COM12 · COM4 · COM5 · COM7",
          "value": 59.0,
          "q1": 58.2,
          "q3": 59.8,
          "n": 10
        },
        {
          "setup": "COM15 · COM13 · COM14 · COM22",
          "value": 58.0,
          "q1": 58.0,
          "q3": 59.0,
          "n": 10
        },
        {
          "setup": "COM4 · COM9 · COM10 · COM20",
          "value": 92.0,
          "q1": 92.0,
          "q3": 92.0,
          "n": 9
        },
        {
          "setup": "COM9 · COM10 · COM11 · COM16",
          "value": 35.0,
          "q1": 34.0,
          "q3": 92.0,
          "n": 9
        },
        {
          "setup": "COM12 · COM14 · COM13 · COM16",
          "value": 36.0,
          "q1": 36.0,
          "q3": 36.5,
          "n": 7
        },
        {
          "setup": "COM13 · COM16 · COM17 · COM19",
          "value": 74.0,
          "q1": 72.5,
          "q3": 74.0,
          "n": 7
        },
        {
          "setup": "COM19 · COM27 · COM26 · COM29",
          "value": 71.0,
          "q1": 71.0,
          "q3": 71.5,
          "n": 7
        },
        {
          "setup": "COM4 · COM13 · COM14 · COM6",
          "value": 92.0,
          "q1": 91.2,
          "q3": 92.0,
          "n": 6
        },
        {
          "setup": "COM4 · COM6 · COM5 · COM0",
          "value": 41.0,
          "q1": 40.0,
          "q3": 49.0,
          "n": 5
        }
      ],
      "name": "Oximeter frame clock against SNUADC",
      "quantity": "Frame period of the PLETH relative to the SNUADC sample clock, fixed per setup (period convention).",
      "display": "+7 to +92 ppm by setup (medians over 42 PC keys); within 3 ppm of its setup's median in 89% of recordings",
      "status": "measured",
      "study": "ppg-frame-clock",
      "about": [
        "oximeter-frames-vs-snuadc"
      ],
      "derived": {}
    },
    "pleth-sawtooth-rate": {
      "id": "pleth-sawtooth-rate",
      "name": "PPG sawtooth rate",
      "quantity": "Creep of the PPG content against the oximeter frames, fixed per setup.",
      "display": "18–52 ppm by setup",
      "status": "measured",
      "study": "ppg-sawtooth",
      "about": [
        "pleth-content-vs-frames",
        "snuadc-pleth"
      ],
      "headline": true,
      "derived": {}
    },
    "pleth-slip-step": {
      "id": "pleth-slip-step",
      "name": "PPG slip size",
      "quantity": "Step of the ECG→PPG delay at a frame slip.",
      "value": 32.3,
      "unit": "ms",
      "spread": {
        "q1": 30.4,
        "q3": 34.8
      },
      "status": "measured",
      "study": "ppg-sawtooth",
      "about": [
        "pleth-content-vs-frames"
      ],
      "derived": {}
    },
    "primus-drift-assumed": {
      "id": "primus-drift-assumed",
      "name": "Primus drift used in the delay fits",
      "quantity": "Drift of the Primus stamps set by hand in the numeric-delay fits and the planner.",
      "value": 115,
      "unit": "ppm",
      "status": "assumed",
      "about": [
        "primus-vs-pc"
      ],
      "notes": "Set by hand before the Primus drift was measured. Breath matching now gives +104 ppm (p10–p90 +28 to +184, [[parameter:primus-drift-breath]]), so 115 ppm lies within the measured spread; the delay fits that used it need no change.",
      "derived": {}
    },
    "primus-drift-breath": {
      "id": "primus-drift-breath",
      "name": "Primus drift against the recorder PC, from breath matching",
      "quantity": "Drift of the Primus stamps against the SNUADC on its stamp model (the recorder PC's timeline), from the slope of the airway-pressure-to-CVP lag within each stretch between Primus AWP pauses, per recording.",
      "value": 104,
      "unit": "ppm",
      "spread": {
        "p10": 28,
        "p90": 184,
        "text": "178 recordings; whole-recording lines give +87 ppm median in 1,060 (p10–p90 −49 to +229)"
      },
      "status": "measured",
      "study": "ventilator-breath-matching",
      "about": [
        "primus-vs-pc"
      ],
      "date": "2026-10-19",
      "headline": true,
      "notes": "Relative to the recorder PC through the SNUADC stamp model, whose own error is a few ppm. The pilot gave +92 ppm (20 cases). The Primus's small correction steps imply only 1–11 ppm ([[parameter:primus-drift-implied]]): they do not track this drift. The lag also steps at the AWP pauses, but by amounts unrelated to the drift built up (median −29 ms, p10–p90 −685 to +494 ms), so the pauses are not drift resets.",
      "derived": {}
    },
    "primus-drift-implied": {
      "id": "primus-drift-implied",
      "name": "Primus drift implied by its corrections",
      "quantity": "Drift of the Primus stamps against the recorder, from correction size over interval.",
      "display": "about 1–11 ppm",
      "status": "derived",
      "study": "correction-periods",
      "about": [
        "primus-vs-pc"
      ],
      "notes": "Correction size over interval. Breath matching measures the Primus drift directly at about +104 ppm ([[parameter:primus-drift-breath]]): these small steps do not track the drift, so this is not the clock's rate.",
      "derived": {}
    },
    "primus-period": {
      "id": "primus-period",
      "name": "Primus correction interval",
      "quantity": "Time between the Primus's small corrections.",
      "value": 7230,
      "unit": "s",
      "display": "about 7,230 s (2.0 h; p90 twice that)",
      "status": "measured",
      "study": "device-correction-schedules",
      "about": [
        "primus-vs-pc"
      ],
      "date": "2026-10-19",
      "notes": "The whole release: 7,230 s or twice that, 8,077 corrections in 4,529 recordings; the AWP pauses recur at the same interval ([[observation:primus-awp-pauses]]). The pilot ([[study:correction-periods]]) gave the same.",
      "derived": {}
    },
    "primus-step": {
      "id": "primus-step",
      "name": "Primus correction size",
      "quantity": "Size of the Primus's small corrections.",
      "value": -0.016,
      "unit": "s",
      "display": "whole 16 ms samples either way; median −16 ms (one sample back), p10–p90 −144 to +144 ms",
      "status": "measured",
      "study": "device-correction-schedules",
      "about": [
        "primus-vs-pc"
      ],
      "date": "2026-10-19",
      "notes": "The whole release: each correction is a whole number of 16 ms samples in either direction, often two at once: 8,077 corrections, median −16 ms, p10–p90 −144 to +144 ms, p5–p95 −176 to +176 ms. The pilot ([[study:correction-periods]]) gave +8 ms median (−64 to +80 ms). These steps do not track the Primus drift ([[parameter:primus-drift-breath]]).",
      "derived": {}
    },
    "recording-pause": {
      "id": "recording-pause",
      "name": "Recording pause",
      "quantity": "Length of the pauses in which every device jumps forward together.",
      "value": 19.6,
      "unit": "s",
      "spread": {
        "p10": 15.7,
        "p90": 29.8,
        "text": "2,025 pauses in 1,846 files (29%)"
      },
      "status": "measured",
      "study": "recorder-pc-steps",
      "about": [
        "recorder-pc"
      ],
      "notes": "The gap survey of every waveform ([[study:continuity-gaps-segments]], 19 October) puts the same pauses as a cliff in the gap distribution of every SNUADC, Primus and BIS track: ECG II has 1,968 gaps of 15–25 s, median 17.9 s. The two medians use different definitions: the survey counts gaps in one track (from the end of one record to the start of the next), while this parameter counts moments when every device jumps together.",
      "derived": {}
    },
    "rr-delay": {
      "id": "rr-delay",
      "name": "Primus RR delay",
      "quantity": "Delay from the end of the 30 s breath-interval window to the stamp of the Primus respiratory rate.",
      "value": 4.3,
      "unit": "s",
      "spread": {
        "p10": 1.6,
        "p90": 7.6
      },
      "status": "measured",
      "study": "numeric-delay-variability",
      "about": [
        "primus-numerics"
      ],
      "derived": {}
    },
    "snuadc-frequent-steps": {
      "id": "snuadc-frequent-steps",
      "name": "SNUADC frequent-correction steps",
      "quantity": "Forward steps of the SNUADC stamps in frequent-correction mode.",
      "display": "+5 to +15 ms (median +10), about one an hour (median 1.1 per hour per recording)",
      "status": "measured",
      "study": "clock-correction-scan",
      "about": [
        "snuadc-frequent-vs-pc"
      ],
      "notes": "The rate is from the correction scan's step counts ([[study:clock-correction-scan]]): 635 small corrections in 448 hours of the 120 recordings in frequent-correction mode, a median of 1.1 per hour per recording (p10–p90 0.5–4.1), that is one every 15 minutes to 2 hours. An earlier figure of 0.2 per hour was not traceable to any result and has been replaced.",
      "derived": {}
    },
    "snuadc-gap-stamp-shift": {
      "id": "snuadc-gap-stamp-shift",
      "name": "Stamp shift across a recording gap",
      "quantity": "Where the SNUADC stamps sit after a 10–18 s recording gap, against the pacing train.",
      "display": "127–149 ms early (4 gaps, 2 cases; modulo one pacing period)",
      "status": "measured",
      "study": "paced-ecg",
      "about": [
        "snuadc-ecg-ii"
      ],
      "date": "2026-10-17",
      "notes": "Measured modulo one pacing period, so a whole number of periods cannot be ruled out. Mechanism open.",
      "derived": {}
    },
    "snuadc-reset-loss": {
      "id": "snuadc-reset-loss",
      "name": "Signal lost at a SNUADC step-mode correction",
      "quantity": "Signal time that is never sampled at each step-mode correction, on the sample count.",
      "value": 17.7,
      "unit": "ms",
      "spread": {
        "min": 17.2,
        "max": 18.2,
        "n": 3,
        "text": "3 corrections in 2 paced cases; ECG–EEG bursts give 13.0 ± 1.7 ms"
      },
      "status": "measured",
      "study": "paced-ecg",
      "about": [
        "snuadc-step-vs-pc",
        "snuadc-ecg-ii"
      ],
      "date": "2026-10-17",
      "notes": "Adopted in the stamp model as 17.7 ms. A refit with this loss left a burst-lag step of −3.3 ± 1.8 ms, which suggests the average loss may be nearer 15 ms.",
      "headline": true,
      "derived": {}
    },
    "snuadc-stamp-arithmetic": {
      "id": "snuadc-stamp-arithmetic",
      "name": "SNUADC stamp arithmetic",
      "quantity": "Slope of the SNUADC block stamps against the sample count between corrections.",
      "value": -0.954,
      "unit": "ppm",
      "spread": {
        "n": 6321,
        "text": "census of 6,321 files (17 October): median −0.9535 ppm; 89.5% of recording hours within 0.05 ppm of −0.954; 747 files (12%) between −0.90 and −0.55 ppm"
      },
      "status": "measured",
      "study": "stamp-arithmetic",
      "about": [
        "snuadc-sample",
        "snuadc-frequent-vs-pc"
      ],
      "derived": {}
    },
    "snuadc-start-delay": {
      "id": "snuadc-start-delay",
      "name": "SNUADC start after the file's first record",
      "quantity": "Delay of the first SNUADC record after the file's first record of any track.",
      "value": 1.56,
      "unit": "s",
      "spread": {
        "text": "5–95%: 0.44–4.5 s; 99th percentile 12 s; maximum 10,879 s"
      },
      "status": "measured",
      "study": "vitaldb-loader",
      "about": [
        "loaded-subset-time-zero",
        "snuadc-ecg-ii"
      ],
      "derived": {}
    },
    "snuadc-step-correction-size": {
      "id": "snuadc-step-correction-size",
      "name": "SNUADC step-mode correction size",
      "quantity": "True size of a step-mode correction, from the pacing train on stamp time.",
      "value": 0.498,
      "unit": "s",
      "display": "about 0.498 s true (paced: 0.4981–0.4988 s); −0.480 s on the stamps",
      "status": "measured",
      "study": "paced-ecg",
      "about": [
        "snuadc-step-vs-pc"
      ],
      "date": "2026-10-17",
      "notes": "Four numbers describe the same reset. The stamps step back −0.4804 s (SD 0.7 ms) at every one of the 3,417 resets in the release ([[study:device-correction-schedules]]); adding the 17.7 ms never sampled ([[parameter:snuadc-reset-loss]]) gives the true 0.498 s seen in the pacing train. The correction scan's −0.487 s (0.484–0.492 s) is measured from the start of the preceding run, so it also holds the −0.954 ppm stamp arithmetic built up over that run, 5.9 ms over the median run of 6,198 s ([[study:clock-correction-scan]]). The 0.49 s quoted as the trigger is the drift at which the reset falls, rounded.",
      "derived": {}
    },
    "snuadc-step-period": {
      "id": "snuadc-step-period",
      "name": "SNUADC step-mode correction interval",
      "quantity": "Time between successive step-mode corrections.",
      "value": 2.08,
      "unit": "h",
      "spread": {
        "text": "5th–95th 1.65–3.57 h between resets; per-recording means p10–p90 1.68–3.07 h"
      },
      "status": "measured",
      "study": "snuadc-stamp-sawtooth",
      "about": [
        "snuadc-step-vs-pc"
      ],
      "date": "2026-10-19",
      "headline": true,
      "notes": "Full release, replacing the pilot value of 2.06 h (p10–p90 1.70–2.31 h, 182 intervals). From the resets of all recordings ([[study:device-correction-schedules]], 19 October): median 2.08 h (5th–95th 1.65–3.57) between resets, in 642 recordings with two or more.",
      "derived": {}
    },
    "snuadc-step-rate": {
      "id": "snuadc-step-rate",
      "setup_key": "PC key: SNUADC · Solar8000 · Primus · BIS serial ports",
      "by_setup": [
        {
          "setup": "COM4 · COM9 · COM10 · COM20",
          "value": -80.3,
          "q1": -80.7,
          "q3": -79.7,
          "n": 61
        },
        {
          "setup": "COM18 · COM13 · COM14 · COM6",
          "value": -72.3,
          "q1": -73.4,
          "q3": -71.9,
          "n": 38
        },
        {
          "setup": "COM7 · COM4 · COM5 · COM9",
          "value": -67.2,
          "q1": -67.7,
          "q3": -66.4,
          "n": 27
        },
        {
          "setup": "COM13 · COM26 · COM27 · COM19",
          "value": -68.4,
          "q1": -68.7,
          "q3": -68.3,
          "n": 25
        },
        {
          "setup": "COM4 · COM9 · COM10 · COM12",
          "value": -65.1,
          "q1": -65.5,
          "q3": -64.9,
          "n": 25
        },
        {
          "setup": "COM13 · COM14 · COM15 · COM19",
          "value": -67.1,
          "q1": -67.7,
          "q3": -66.8,
          "n": 23
        },
        {
          "setup": "COM3 · COM4 · COM5 · COM7",
          "value": -61.1,
          "q1": -63.3,
          "q3": -60.7,
          "n": 23
        },
        {
          "setup": "COM12 · COM4 · COM5 · COM7",
          "value": -60.8,
          "q1": -61.2,
          "q3": -60.1,
          "n": 21
        },
        {
          "setup": "COM4 · COM5 · COM6 · COM20",
          "value": -81.5,
          "q1": -82.7,
          "q3": -81.0,
          "n": 20
        },
        {
          "setup": "COM4 · COM5 · COM6 · COM12",
          "value": -67.1,
          "q1": -67.2,
          "q3": -66.9,
          "n": 18
        },
        {
          "setup": "COM45 · COM42 · COM43 · COM39",
          "value": -60.1,
          "q1": -61.4,
          "q3": -59.2,
          "n": 16
        },
        {
          "setup": "COM25 · COM26 · COM27 · COM29",
          "value": -59.7,
          "q1": -60.6,
          "q3": -59.2,
          "n": 13
        },
        {
          "setup": "COM4 · COM13 · COM14 · COM6",
          "value": -79.2,
          "q1": -79.5,
          "q3": -78.8,
          "n": 13
        },
        {
          "setup": "COM9 · COM10 · COM11 · COM16",
          "value": -80.5,
          "q1": -80.9,
          "q3": -80.1,
          "n": 13
        },
        {
          "setup": "COM4 · COM26 · COM27 · COM29",
          "value": -64.9,
          "q1": -65.5,
          "q3": -64.7,
          "n": 12
        },
        {
          "setup": "COM8 · COM42 · COM43 · COM39",
          "value": -60.2,
          "q1": -61.2,
          "q3": -59.5,
          "n": 12
        },
        {
          "setup": "COM13 · COM4 · COM5 · COM9",
          "value": -66.9,
          "q1": -67.1,
          "q3": -65.4,
          "n": 11
        },
        {
          "setup": "COM4 · COM11 · COM12 · COM8",
          "value": -60.1,
          "q1": -60.7,
          "q3": -59.2,
          "n": 11
        },
        {
          "setup": "COM13 · COM4 · COM5 · COM23",
          "value": -66.2,
          "q1": -66.4,
          "q3": -66.1,
          "n": 9
        },
        {
          "setup": "COM8 · COM14 · COM15 · COM4",
          "value": -60.5,
          "q1": -61.3,
          "q3": -59.9,
          "n": 8
        },
        {
          "setup": "COM19 · COM26 · COM27 · COM29",
          "value": -50.6,
          "q1": -53.9,
          "q3": -47.6,
          "n": 6
        },
        {
          "setup": "COM14 · COM21 · COM22 · COM18",
          "value": -47.4,
          "q1": -47.5,
          "q3": -47.1,
          "n": 5
        },
        {
          "setup": "COM9 · COM15 · COM16 · COM20",
          "value": -68.1,
          "q1": -68.6,
          "q3": -67.0,
          "n": 5
        }
      ],
      "name": "SNUADC step-mode drift",
      "quantity": "Drift of the SNUADC stamps against the recorder PC in step mode, per case.",
      "value": -63,
      "unit": "ppm",
      "spread": {
        "p10": -79,
        "p90": -44,
        "text": "5th–95th −80.3 to −36.9 ppm; medians of −46 to −74 ppm by serial port"
      },
      "status": "measured",
      "study": "snuadc-stamp-sawtooth",
      "about": [
        "snuadc-step-vs-pc"
      ],
      "date": "2026-10-19",
      "notes": "Full release, replacing the pilot value of −62 ppm (p10–p90 −71 to +7). The size of each correction divided by the time since the previous one, from the record stamps. From the resets of all recordings ([[study:device-correction-schedules]], 19 October): median −62.9 ppm (5th–95th −80.3 to −36.9) in 642 recordings with two resets or more; the full census fit gives 2,728 recordings, median −49.5 ppm (5th–95th −79.1 to −8.1). Relative to the recorder PC.",
      "headline": true,
      "derived": {}
    },
    "solar-delay-drift": {
      "id": "solar-delay-drift",
      "name": "Drift of Solar8000 numeric delays within a case",
      "quantity": "Change of a Solar8000 numeric's delay through a case, per hour.",
      "display": "0 s/h median (p10–p90 about ±0.5 s/h)",
      "status": "measured",
      "study": "numeric-delay-variability",
      "about": [
        "solar8000-vs-snuadc"
      ],
      "derived": {}
    },
    "spo2-onset": {
      "id": "spo2-onset",
      "name": "SpO2 onset latency",
      "quantity": "Time from a PLETH-only artefact to the start of the SpO2 response, relative to the recorded PLETH.",
      "display": "about 4 s to the start of the dip, 8 s to its deepest; window 10–20 s (estimated)",
      "status": "measured",
      "study": "spo2-latency",
      "about": [
        "solar8000-spo2"
      ],
      "derived": {}
    },
    "stamp-model-error": {
      "id": "stamp-model-error",
      "name": "EEG–ECG timing error after the stamp model",
      "quantity": "EEG–ECG timing error per 10-minute window with the stamp model and the SNUADC mode's constant.",
      "value": 0.042,
      "unit": "s",
      "display": "42 ms median, p90 150 ms (17 ms and 79 ms with the recording's own bursts)",
      "status": "measured",
      "study": "snuadc-stamp-sawtooth",
      "about": [
        "bis-vs-snuadc",
        "bis-eeg"
      ],
      "headline": true,
      "date": "2026-10-19",
      "notes": "The whole release (2,710 recordings with at least 5 good burst windows, model3). The pilot gave 33 ms median, p90 116 ms (16 and 67 ms with the case's own bursts) in 143 cases; the whole release includes recordings with no correction, with mode changes and with many gaps, which the pilot's cases mostly lacked.",
      "derived": {}
    }
  },
  "studies": {
    "airway-heartbeat-pilot": {
      "id": "airway-heartbeat-pilot",
      "name": "The heartbeat in the Primus airway signals",
      "summary": "A pilot in 30 recordings for a heartbeat-locked route from the Primus to the SNUADC. Airway pressure shows the heartbeat in 9% of windows only. CO2 shows a heartbeat-locked trace in 60% of windows, tight to 20 ms, but its lag does not follow the Primus drift, so it cannot time the Primus. Closed as a clock route.",
      "date": "2026-10-19",
      "status": "current",
      "methods": [
        "shared-events"
      ],
      "techniques": [
        "template-beat-timing",
        "null-controls"
      ],
      "clocks": [
        "primus-clock",
        "snuadc-sample"
      ],
      "devices": [
        "primus",
        "snuadc"
      ],
      "variables": [
        "primus-awp",
        "primus-co2",
        "snuadc-ecg-ii"
      ],
      "data": "30 ventilated recordings with AWP, CO2, ECG and CVP, chosen across setups; 1,608 (AWP) and 1,604 (CO2) windows.",
      "method": "R peaks on the ECG's stamp-model timeline. AWP and CO2 high-passed at 0.6 Hz, the breath-locked shape subtracted\nand expiration masked; an R-triggered average per window, its lag tracked from window to window. A control\ntriggers on R times jittered by up to half a beat; the peak-to-noise ratio (PNR) is against it. The breath-matched\n(CVP) lag of the same windows gives the Primus drift to compare with.\n",
      "results": "| Signal | Windows with PNR ≥ 5 | Lag scatter (MAD) | Slope against breath slope |\n|---|---|---|---|\n| AWP | 9% | 22 ms | r = 0.02 (9 stretches) |\n| CO2 | 60% | 20 ms | r = −0.02 (56 stretches); median difference −114 ppm |\n\nThe CO2 trace is flat on the SNUADC timeline, while the Primus drifts by about +104 ppm against it.\n",
      "discussion": "The CO2 waveform carries a clear heartbeat-locked signal, but it does not move with the Primus clock, so it is not\na Primus timing route ([[observation:co2-cardiac-trace]]). Airway pressure carries too little cardiac signal once\nthe breath shape is removed.\n",
      "limitations": "A 30-recording pilot; the jitter control guards against breath-heart coupling but not every artefact.",
      "code": [
        "vitaldb-bench/tools/airway_heartbeat_pilot.py"
      ],
      "derived": {
        "observations": [
          "co2-cardiac-trace"
        ],
        "code_urls": {
          "vitaldb-bench/tools/airway_heartbeat_pilot.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/airway_heartbeat_pilot.py"
        }
      }
    },
    "arterial-line-accesses": {
      "id": "arterial-line-accesses",
      "name": "Arterial line accesses in the pressure waveform",
      "summary": "Flushes, zeroing and blood draws leave clear signatures in the arterial pressure. A detector finds them in the waveform alone (about one access per hour) and matches 77% of lab draws to an access within 15 minutes, against 26% for random times.",
      "date": "2026-10-11",
      "status": "current",
      "methods": [
        "clinical-time",
        "signal-continuity"
      ],
      "techniques": [
        "null-controls"
      ],
      "devices": [
        "snuadc",
        "solar8000",
        "clinical-records"
      ],
      "variables": [
        "snuadc-art",
        "lab-blood-gas",
        "lab-central",
        "solar8000-nibp",
        "snuadc-pleth",
        "snuadc-ecg-ii"
      ],
      "data": "Every case with three or more blood draws during surgery plus the 204 benchmark cases with an arterial line, 1,440 cases and 7,507 hours; 50 accesses checked by eye.",
      "background": "A blood sample for the lab is drawn from the arterial line, which interrupts the pressure trace. Finding those\ninterruptions gives the draw time, which the lab table does not record, and marks stretches where arterial timing\nshould not be trusted.\n",
      "method": "[[software:lineaccess]] (a standalone MIT module) classifies 1 s windows as high (a flush from the pressure bag, over\n200 mmHg), low (open to air, zeroing, disconnection, under 10 mmHg) or flat (no pulse, after removing a linear\ntrend), merges them into events, groups events less than 90 s apart into one access, and gives each a recovery\nperiod until the pulse is back to 70% of its size. A lost pulse with no flush or zero is an occlusion; stretches\nover 5 minutes are unavailable. Accesses were matched to lab times, and arterial-side timing steps (ECG→ABP moves,\nECG→PPG does not) to accesses, with random times as controls.\n",
      "results": "- **8,245 accesses (1.1 per hour), 3,249 occlusions (0.4 per hour), 590 hours unavailable.**\n- **Precision 33 of 34** on a visual check of 50 random events. Most \"lost pulse only\" events were a blood\n  pressure cuff inflating on the arm with the line: 66% have an NIBP reading 0–90 s later, against 24% at random.\n- **Lab draws:** 76.6% follow an access within 15 minutes, against 26% for random times.\n- **Arterial-side timing steps cluster at line events:** 24% fall within 60 s of an access or occlusion, against\n  4.5% at random. Cuff inflations disturb arterial timing as much as line accesses do.\n- **Blood-gas stamps follow an access by two typical delays,** about 4 and about 9 minutes (median 410 s over 4,742\n  matched gases); random times in the same recordings show no such peak.\n\n[[figure:arterial-line-accesses]]\n",
      "discussion": "The access is the best available estimate of a lab draw, and the lab_draw label marks it where one matched.\nArterial timing (for example as a check on the PPG correction) should skip accesses and occlusions.\n",
      "limitations": "No ground truth for accesses; recall is unchecked. Long real stretches without a pulse (for example bypass) would be labelled unavailable.",
      "code": [
        "lineaccess",
        "vitaldb-bench/tools/line_access_scan.py",
        "vitaldb-bench/tools/lab_draw_labels.py"
      ],
      "figures": [
        {
          "id": "arterial-line-accesses",
          "file": "arterial-line-accesses.png",
          "theme": "example traces",
          "source": "vitaldb-bench tools/line_access_figure.py",
          "records": [
            1
          ],
          "caption": "Left: arterial pressure around one detected access in [[case:1]]: the pulse is lost, the line is flushed (high pressure from the bag), and the pulse returns. Right: time from the start of the latest access to each blood-gas stamp (blue), against random times in the same recordings (grey; 30% of them have an access in the 15 minutes before). Central-lab panels, stamped later, are left out.",
          "alt": "Left, an arterial pressure trace with three flush upstrokes to about 350 mmHg inside a shaded access. Right, a histogram with peaks near 220 s and 520 s above a flat grey control."
        }
      ],
      "derived": {
        "parameters": [
          "line-access-rate"
        ],
        "observations": [
          "arterial-steps-at-line-events",
          "cuff-occludes-line",
          "lab-time-is-specimen"
        ],
        "examples": [
          "gas-gradients-at-the-draw"
        ],
        "code_urls": {
          "lineaccess": "https://github.com/metrology-atlas/lineaccess",
          "vitaldb-bench/tools/line_access_scan.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/line_access_scan.py",
          "vitaldb-bench/tools/lab_draw_labels.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/lab_draw_labels.py"
        }
      }
    },
    "bis-cardiac-artefact": {
      "id": "bis-cardiac-artefact",
      "name": "The cardiac artefact in the BIS EEG",
      "summary": "The BIS EEG carries a weak electrical trace of every heartbeat. Too weak for beat-by-beat matching, it shows clearly when averaged on the ECG's R peaks, and gives a second SNUADC–BIS timing reference that agrees with the bursts to about 16 ms.",
      "date": "2026-10-12",
      "status": "current",
      "methods": [
        "shared-events"
      ],
      "techniques": [
        "null-controls"
      ],
      "clocks": [
        "bis-clock",
        "snuadc-sample"
      ],
      "devices": [
        "bis",
        "snuadc"
      ],
      "variables": [
        "bis-eeg",
        "snuadc-ecg-ii"
      ],
      "data": "30 benchmark cases with BIS (a first attempt on 28), three 10-minute windows per channel each.",
      "background": "Heartbeat interval matching aligns EEG with ECG when the EEG carries the heartbeat (Goodwin et al. 2023). A first\nattempt found no usable heartbeat in the BIS EEG; it searched −0.3 to +0.5 s around each R peak on raw stamps,\nwhile the same event is stamped about 0.8 s earlier in BIS.\n",
      "method": "Both devices on stamp-model time; R-triggered averages of the EEG from −2 to +1 s, in three 10-minute windows per\nchannel per case; the peak lag compared with the burst-measured offset.\n",
      "results": "- **A clear peak** (peak-to-noise ≥ 8) in 44 of 180 windows, in 13 of 30 cases; weaker peaks at the same lag in\n  many more. Median amplitude 4.3 µV.\n- **Its lag matches the burst offset** to a median of −16 ms (p10–p90 −31 to +9 ms): electrical, near-simultaneous\n  with the R wave.\n- **Single beats are too weak:** single-beat SNR 0.4 median, so beat-by-beat interval matching stays impractical.\n",
      "discussion": "The averaged artefact is a second, independent SNUADC–BIS reference, including in cases with no bursts. It\nsupersedes the first attempt's conclusion that the BIS EEG carries no heartbeat.\n",
      "open_questions": [
        {
          "title": "Why the artefact shows in only some cases",
          "kind": "phenomenon",
          "summary": "A clear averaged artefact appears in 13 of 30 cases. What decides whether it is visible (electrode placement, signal quality, filtering) is not known."
        }
      ],
      "code": [
        "vitaldb-bench/tools/eeg_sync_pilot.py",
        "vitaldb-bench/tools/eeg_cardiac_recheck.py"
      ],
      "references": [
        "goodwin2023"
      ],
      "derived": {
        "observations": [
          "bis-eeg-cardiac-artefact"
        ],
        "code_urls": {
          "vitaldb-bench/tools/eeg_sync_pilot.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/eeg_sync_pilot.py",
          "vitaldb-bench/tools/eeg_cardiac_recheck.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/eeg_cardiac_recheck.py"
        }
      }
    },
    "bis-index-delay": {
      "id": "bis-index-delay",
      "name": "How the BIS index trails its EEG",
      "summary": "Modelling the BIS index as a lagged mix of EEG features shows a trailing average starting about 3 s after its EEG window ends: about 16 s long in half the cases (the 15 s smoothing setting) and 32–61 s in most others. Half the response weight is older than 30 s.",
      "date": "2026-10-13",
      "status": "current",
      "methods": [
        "delays-and-windows"
      ],
      "devices": [
        "bis"
      ],
      "variables": [
        "bis-index",
        "bis-eeg"
      ],
      "data": "All 5,853 cases with a BIS module; 5,520 fitted, 3,297 good fits (median R² 0.81).",
      "method": "EEG features at 1 Hz from BIS EEG1, each stamped at the end of its 2 s window (five band powers, total power,\nSEF95, beta ratio, suppression). The index is a weighted sum of the features passed through a lag kernel over −10\nto 120 s, fitted per case where the signal quality index is at least 50; the kernel is split into a trailing\nboxcar (the fast part), a slow exponential tail and a floor. Index and EEG share the monitor's own stamps, so no\nclock correction is needed. Checked on synthetic index values (15, 30 and 45 s windows come back as 15, 28 and 46 s)\nand on the suppression ratio (a known ~63 s window, read as 77 s).\n",
      "results": "- **Delay 3 s** from the EEG window's end (p10–p90 3–4 s), the same in every group.\n- **15 s setting:** 1,578 of 3,297 good fits (48%) have a fast part of 13–17 s: about 16 s of EEG ending 3 s before\n  the stamp. 1,304 (40%) are longer, 32–61 s (median 50 s; less certain). Five PCs never show the 15 s window.\n- **Most of the response is slow:** the fast part holds 16% of the kernel weight; half the weight is older than\n  30 s, 90% older than 97 s.\n",
      "discussion": "Treat the index as an average of the EEG over roughly 15–40 s ending about 3 s before its stamp, with slow weight\nbeyond. It suits slow questions (depth of anaesthesia over minutes); anything faster needs features computed from\nthe EEG itself, which after synchronisation sits about 42 ms from SNUADC time (17 ms with the recording's own bursts;\n[[parameter:stamp-model-error]]). The 132 s propofol-to-BIS latency\n([[study:drug-effect-latency]]) includes this delay. The bis_smoothing label gives each case's group.\n",
      "code": [
        "vitaldb-bench/tools/bis_index_delay.py",
        "vitaldb-bench/tools/bis_smoothing_labels.py"
      ],
      "references": [
        "pilge2006timedelay",
        "rampil1998primer"
      ],
      "derived": {
        "parameters": [
          "bis-index-delay",
          "bis-index-window"
        ],
        "observations": [
          "bis-index-slow",
          "numerics-not-front-stamped"
        ],
        "code_urls": {
          "vitaldb-bench/tools/bis_index_delay.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/bis_index_delay.py",
          "vitaldb-bench/tools/bis_smoothing_labels.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/bis_smoothing_labels.py"
        }
      }
    },
    "case-clock": {
      "id": "case-clock",
      "name": "Recovering the time of day",
      "summary": "The release shifts every case to start at 2100-01-01 00:00 UTC, removing the date and the time of day. The time of day (Korean local time) can be recovered to the minute from the admission date and the seconds of the EMR times.",
      "date": "2026-10-12",
      "status": "current",
      "methods": [
        "clinical-time"
      ],
      "clocks": [
        "recorder-pc",
        "hospital-records"
      ],
      "devices": [
        "vital-recorder",
        "clinical-records"
      ],
      "variables": [
        "emr-events",
        "admission-discharge"
      ],
      "data": "All 6,388 rows of clinical_data.csv: the admission date (adm) and the four EMR times (anestart, opstart,\naneend, opend). A clock was recovered for 6,385 cases: all four EMR times share one seconds phase in 5,774 and three\nof four in 611. Three cases get none (adm not on a whole minute in two, EMR times disagree in one). No waveform\ndata were needed.\n",
      "background": "The recorder writes Unix time (UTC). The release then shifts each case so its start reads 2100-01-01 00:00 UTC.\nLocal Korean time (KST, UTC+9, no daylight saving since 1988) enters through the hospital systems: admission and\ndischarge are local dates, and the EMR times are local clock times relative to the case start.\n",
      "method": "The admission date is a local midnight expressed relative to the case start, so −adm modulo one day gives the case\nstart's time of day; the seconds of the EMR times refine it. The case clock is accepted to the minute where three\nor more EMR times agree.\n",
      "results": "- **Time of day to the minute** in cases where at least three EMR times agree ([[code:vitaldb-bench/vitaldb_bench/case_clock.py|vitaldb_bench.case_clock]]).\n- **The case clock is KST;** subtracting 9 h returns to UTC.\n- **It reveals which EMR entries fall on 5-minute marks,** the basis of [[study:emr-rounding]].\n",
      "discussion": "One assumption cannot be tested in the release: that the recorder PC's time zone was set correctly, so its Unix\ntime was true UTC rather than local time read as UTC. The EMR clock's own offset from the recorder cannot be\nseparated from documentation lag ([[study:loop-closure]]).\n",
      "open_questions": [
        {
          "title": "The recorder PC's time zone",
          "kind": "assumption",
          "summary": "Whether its Unix time was true UTC, or local time read as UTC, cannot be tested in the release."
        },
        {
          "title": "The PC-to-UTC link",
          "kind": "assumption",
          "summary": "Every route from a device clock to UTC passes through the recorder PC's clock, which is assumed to have been kept on UTC by network time. The release holds no second, independent clock against which to test it, so this link is assumed, not measured."
        }
      ],
      "code": [
        "vitaldb-bench/vitaldb_bench/case_clock.py"
      ],
      "derived": {
        "observations": [
          "release-time-shift"
        ],
        "code_urls": {
          "vitaldb-bench/vitaldb_bench/case_clock.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/vitaldb_bench/case_clock.py"
        }
      }
    },
    "clock-correction-scan": {
      "id": "clock-correction-scan",
      "name": "Clock corrections in every file",
      "summary": "A scan of all 6,388 files for the jumps where the recorder re-synchronises a device's stamps. Each device type has its own correction signature; only SNUADC and BIS correct clearly enough to measure their drift.",
      "date": "2026-10-11",
      "status": "current",
      "methods": [
        "stamp-reading",
        "signal-continuity"
      ],
      "techniques": [
        "coincident-jumps",
        "junction-scores",
        "null-controls"
      ],
      "clocks": [
        "snuadc-sample",
        "bis-clock",
        "cardioq-clock",
        "primus-clock",
        "recorder-pc"
      ],
      "devices": [
        "snuadc",
        "bis",
        "cardioq",
        "primus",
        "vital-recorder"
      ],
      "variables": [
        "snuadc-ecg-ii",
        "snuadc-art",
        "snuadc-pleth",
        "bis-eeg",
        "primus-co2"
      ],
      "data": "All 6,388 original .vital files of the release (102.4 GB), read with the shared reader. 1.4 CPU-hours; only\naggregates left the analysis machine.\n",
      "background": "Between corrections, a device's waveform stamps advance by exactly the number of samples each record holds. A\ncorrection shows as a jump at a record boundary. If the jump is a clock correction, its size divided by the time\nsince the previous one is the device's drift against the recorder PC.\n",
      "method": "Each waveform track was split into runs of records that join to within half a sample. Runs shorter than 5 s\nwere folded into the boundary between the long runs either side (they come in bursts), leaving 94,152\nboundaries. A boundary counted as a correction candidate when its jump was at most 2 s, it was not a step back in\ntime, and every other waveform track of the same device jumped by the same amount (within 2 ms) at the same\nmoment. Drift came only from a candidate whose previous boundary was also a candidate, so a case's first interval\nwas never used: drift = jump ÷ time since the previous correction, one duration-weighted median per case and\ndevice.\n\nAround candidate boundaries, and at random joins inside long runs as controls, a few seconds of ECG, arterial\npressure, plethysmogram, EEG and CO2 were kept, to test the beat interval and the sample-to-sample step across\neach join.\n",
      "results": "| Device | Candidates | Usable for drift | Typical jump | Signature |\n|---|---|---|---|---|\n| SNUADC | 3,878 | 818 | −0.487 s | A fixed pull-back of about 0.49 s, as one overlap or a burst of short records |\n| BIS | 7,655 | 1,974 | +0.15 s (5–95%: +0.01 to +0.41 s) | The run is cut every 8,192 s; the gap at the cut is the drift since the last cut |\n| CardioQ | 9,931 | 5 | spread across ±2 s | Irregular re-anchoring; not usable for drift |\n| Primus | 66 | 2 | scattered | Waveforms come in sessions separated by long gaps |\n\n- **A correction moves the whole device.** 13,955 of 13,958 sub-second SNUADC overlaps hit every SNUADC track\n  at the same moment by the same amount; for BIS, every one.\n- **No time is lost at a SNUADC step, at this resolution.** Butting the samples together keeps the beat-to-beat\n  interval normal in 59% of ECG joins; inserting the jump as a real gap does so in only 17%.\n- **The records inside the burst join badly.** At their joins the sample-to-sample step is larger than 95% of\n  normal steps in 60–96% of cases, against about 4% at ordinary joins. (Later explained: the records are written\n  out of order; see [[study:reset-cluster-order]].)\n- **Small positive SNUADC gaps are real data loss**, not corrections.\n- **Two other SNUADC behaviours.** 120 cases correct in small steps (−0.002 to +0.017 s every 15–50 min) and\n  never by 0.49 s; their stamps follow the PC. 1,066 cases of 2.5 h or longer have no correction at all.\n",
      "discussion": "A correction can be recognised by its pattern (SNUADC's 0.484–0.492 s step, BIS's cut at 8,192 s), by every track\nof the device jumping together, and by an implied rate that fits the case. Other jumps on these devices are\ngaps or dropouts. Within a case the rate holds to about 0.2 ppm, so one rate per device per case is enough. The\ndrift values from this scan are wrong for some cases; the stamp sawtooth model\n([[study:snuadc-stamp-sawtooth]]) replaced them. The SNUADC jump here (−0.487 s median) is measured from the start\nof the run before it, so it includes the −0.954 ppm stamp arithmetic built up over that run\n([[study:stamp-arithmetic]]): over the median run of 6,198 s that is 5.9 ms, and −0.480 − 0.006 = −0.486 s. The\nreset itself steps the stamps by −0.4804 s (SD 0.7 ms, all 3,417 resets in the release;\n[[study:device-correction-schedules]]), and the true correction is about 0.498 s once the 17.7 ms of signal lost\nat the reset is counted ([[parameter:snuadc-step-correction-size]]). The Primus row is a miss, not an absence: the Primus does correct,\nevery 7,230 s by whole 16 ms samples, but its AWP pauses at the same moments while CO2 carries on, so the\nsame-jump test rejected nearly all of them ([[study:device-correction-schedules]]).\n",
      "limitations": "The 2 s limit and the same-jump test can miss corrections during gaps. The cases with no correction at all are not\nyet explained (slower units, a third recorder mode, or both); the shared bursts point to slower units\n([[observation:snuadc-long-uncorrected]]).\n",
      "open_questions": [
        {
          "title": "Recordings that never correct their clocks",
          "kind": "phenomenon",
          "summary": "Some recordings never correct their clocks within the 2 s test. Slower units, a third recorder mode, or both, could explain it. The shared bursts point to slower units (about −36 ppm where the BIS rate is known; [[observation:snuadc-long-uncorrected]]); their drift units have not been compared with the rest. Followed up on [[study:snuadc-stamp-sawtooth]]."
        }
      ],
      "code": [
        "vitaldb-bench/tools/syncscan.py",
        "vitaldb-bench/tools/syncanalyze.py"
      ],
      "figures": [
        {
          "id": "sync-join-tests",
          "file": "sync-join-tests.png",
          "theme": "correction intervals",
          "source": "vitaldb-bench tools/sync_figures.py",
          "caption": "Continuity of the signal across corrections and other record boundaries, against boundaries with no correction.",
          "alt": "Five step-histogram panels comparing controls, correction candidates and other boundaries."
        },
        {
          "id": "sync-correction-jumps",
          "file": "sync-correction-jumps.png",
          "theme": "correction intervals",
          "source": "vitaldb-bench tools/sync_figures.py",
          "caption": "Size of the stamp jump at each clock correction: SNUADC steps back about 0.48 s or makes millisecond corrections; BIS jumps forward 0–0.5 s; CardioQ is broad.",
          "alt": "Three stacked histograms of jump size in seconds, one per device."
        }
      ],
      "derived": {
        "parameters": [
          "snuadc-frequent-steps"
        ],
        "observations": [
          "device-drift-by-setup",
          "reset-records-out-of-order",
          "small-snuadc-gaps",
          "snuadc-long-uncorrected",
          "snuadc-no-timestamps",
          "snuadc-one-clock"
        ],
        "code_urls": {
          "vitaldb-bench/tools/syncscan.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/syncscan.py",
          "vitaldb-bench/tools/syncanalyze.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/syncanalyze.py"
        }
      }
    },
    "continuity-gaps-segments": {
      "id": "continuity-gaps-segments",
      "name": "Gaps and unbroken segments in every waveform",
      "summary": "Every gap and every unbroken segment in the 6,388 recordings, per waveform. The curves are not smooth: a cliff at about 18 s in every device marks the recorder's pauses, SNUADC segments stop at 5 hours, Primus segments at about 2.0 hours and BIS segments at about 2.3 hours (each device's correction cycle), and BIS's short gaps grow with the time since the last one (clock resynchronisations, not lost data).",
      "date": "2026-10-19",
      "status": "draft",
      "methods": [
        "data-continuity"
      ],
      "techniques": [
        "censored-durations",
        "null-controls"
      ],
      "clocks": [
        "recorder-pc",
        "snuadc-sample",
        "bis-clock",
        "primus-clock"
      ],
      "devices": [
        "vital-recorder",
        "snuadc",
        "primus",
        "bis",
        "cardioq"
      ],
      "variables": [
        "snuadc-ecg-ii",
        "snuadc-pleth",
        "snuadc-art",
        "primus-co2",
        "primus-awp",
        "bis-eeg"
      ],
      "code": [
        "vitaldb-bench/tools/signal_events.py",
        "vitaldb-bench/tools/signal_events_collect.py",
        "vitaldb-bench/tools/continuity_figs.py",
        "vitaldb-bench/tools/continuity_sync.py"
      ],
      "data": "All 6,388 recordings; every waveform track (SNUADC ECG II, V5, PLETH, ART, CVP, FEM; Primus CO2 and AWP; BIS\nEEG; CardioQ FLOW). A gap is any forward step in the stamps of more than 1.5 samples beyond the end of the\nprevious record; a segment is a stretch with no gap.\n",
      "background": "Gaps that strike at random give a smooth distribution of gap lengths and of segment lengths. Any mechanism with\na length of its own (a restart, a periodic break, a buffer) shows as a cliff or a step. Before choosing how to\nhandle missing data, the question is which gaps are which.\n",
      "method": "- Per recording and waveform, every gap (start, length) and every segment (start, length, and whether it ends\n  in a gap or at the edge of the recording) was cached (`signal_events.py`) and collected into one table.\n- Cumulative counts (number longer than x) on log–log axes, for gaps and for segments; for segments, all,\n  interior only (a gap at both ends), and a Kaplan–Meier estimate with edge segments censored.\n- Each gap's length against the time since the previous gap in the same track (or since the start).\n",
      "results": "- **A cliff at about 18 s in every device** (ECG II: 1,968 gaps of 15–25 s, median 17.9 s; the same in PLETH,\n  ART, CVP, Primus and BIS). These are the recorder's pauses ([[observation:recording-pauses]]), common to\n  every device, not losses in any one signal. Above about 30 s the curve falls smoothly to gaps of hours.\n- **A step at 0.2 s in SNUADC:** gaps of exactly one message.\n- **SNUADC segments stop at 5 hours.** No interior segment is longer than 17,991 s (5 h minus about 9 s), and\n  many are exactly that long, each followed by an 18 s gap. These are the recorder's pauses, which recur every\n  5 hours on a timer of their own ([[observation:recording-pauses]]); most 5-hour segments start at an earlier\n  pause.\n- **Primus and BIS segments stop at their correction cycles.** Primus AWP segments never exceed about 7,980 s; most\n  end at the AWP pause that recurs every 7,230 s (2.0 h; [[observation:primus-awp-pauses]]). BIS segments pile up\n  at 7,500–8,500 s (2,584 of them), around its 8,192 s (2.3 h) correction timer\n  ([[study:device-correction-schedules]]).\n- **BIS's short gaps are clock resynchronisations.** Sub-second BIS gaps grow in proportion to the time since the\n  previous gap: a median 30 ppm of the elapsed time (interquartile range 20–48 ppm), the BIS clock's drift\n  against the recorder (18–55 ppm by setup; [[observation:device-drift-by-setup]]). The stamps are stepped to\n  catch up; no samples are lost.\n- **AWP has an arc of sub-second gaps** whose length plus the segment before it is about 0.5 s: bursts on a\n  0.5 s cycle, one Primus message.\n- **CardioQ is fragmented throughout:** about 76 gaps per hour, median coverage 80%, with no structure beyond a\n  smooth curve.\n- **SNUADC has a band of millisecond gaps right after a 0.2 s segment:** one record alone, then a small step.\n- Most segments end with the recording, not with a gap, so the censored estimate stays high; the interior and\n  all-segment curves bracket it.\n\n[[figure:continuity-gaps-ccdf]]\n\n[[figure:continuity-segments-ccdf]]\n\n[[figure:continuity-gap-vs-since]]\n\n**Before and after synchronisation.** Measured again at every record junction, on the stamps and on each device's\nfitted stamp clock (6,322–6,342 recordings per signal; up to 28 per signal with broken record streams left out,\n[[observation:duplicated-records]], [[observation:interleaved-records]], [[observation:snuadc-overlapping-records]]):\n\n| Signal | Gaps ≥ 10 ms, as recorded → synchronised | Overlaps ≥ 10 ms, as recorded → synchronised |\n|---|---|---|\n| SNUADC (ECG II) | 5,621 → 8,850 | 14,825 → 29 |\n| BIS EEG | 10,545 → 3,322 | 1,306 → 1,338 |\n| Primus AWP | 20,260 → 15,485 | 7,291 → 1,679 |\n| Primus CO2 | 3,799 → 3,798 | 1,675 → 67 |\n\nClock corrections look like gaps or overlaps on the stamps and vanish once the clock is fitted: the SNUADC's\nresets and the reordered records around them (overlaps), the BIS's forward corrections of about 0.17 s (gaps),\nand the Primus's small whole-sample steps. The SNUADC gains about 3,200 gaps, each the 17.7 ms its reset never\nsamples ([[observation:reset-signal-lost]]): signal really missing, which the stamps hide. Gaps of a second or more\nare the same before and after: they are real. About 1,300 short BIS overlaps run against its correction\ndirection and are kept as breaks.\n\n[[figure:continuity-sync]]\n",
      "discussion": "Almost none of the structure is random. The largest groups of gaps are system events (pauses, periodic\nbreaks, resynchronisations) with lengths of their own, and the remaining smooth tail is a small share. The\nforward gaps seen here are only part of the picture: the stamps also step backwards far more often\n([[study:record-messages-and-steps]]).\n",
      "limitations": "- The gap rule looks forward only and ignores steps under 1.5 samples.\n- The 5-hour and 2.2-hour breaks are inferred from the stamps; the recorder's own logs, if they exist, would\n  name them.\n",
      "open_questions": [
        {
          "title": "Which gaps lose data?",
          "kind": "next step",
          "summary": "Answered per class in [[study:step-classification]]: shared pauses and most SNUADC gaps beyond one message lose data; sub-message steps and BIS resynchronisations do not."
        }
      ],
      "figures": [
        {
          "id": "continuity-gaps-ccdf",
          "file": "continuity-gaps-ccdf.png",
          "theme": "data continuity",
          "source": "vitaldb-bench tools/continuity_figs.py",
          "caption": "Number of gaps longer than x, per waveform, log–log; dotted lines at 0.2 s, 1 s, 1 min and 1 h. Every SNUADC, Primus and BIS signal has a cliff at about 18 s (the recorder's pauses); SNUADC also steps at 0.2 s (one message). CardioQ falls smoothly from about 1 s.",
          "alt": "Ten log–log step curves of gap counts, most with a sharp cliff near 18 seconds and a smooth tail to hours."
        },
        {
          "id": "continuity-segments-ccdf",
          "file": "continuity-segments-ccdf.png",
          "theme": "data continuity",
          "source": "vitaldb-bench tools/continuity_figs.py",
          "caption": "Number of unbroken segments longer than x, per waveform: all segments (grey), interior segments with a gap at both ends (blue), and a Kaplan–Meier estimate with edge segments censored (orange). SNUADC interior segments stop at 5 hours; Primus AWP segments near 2.0 hours and BIS segments near 2.3 hours.",
          "alt": "Ten log–log curves of segment counts; the blue SNUADC curves drop vertically at about 18,000 seconds, Primus and BIS at about 8,000 seconds."
        },
        {
          "id": "continuity-gap-vs-since",
          "file": "continuity-gap-vs-since.png",
          "theme": "data continuity",
          "source": "vitaldb-bench tools/continuity_figs.py",
          "caption": "Each gap's length (x) against the time since the previous gap in the same track (y), counts on a log colour scale. The column at 18 s is the recorder's pauses; BIS's diagonal band (short gaps growing with the elapsed time) is clock resynchronisation; AWP's arc is gaps plus segments of 0.5 s.",
          "alt": "Ten heat maps; each has a dark column near 18 seconds; the BIS panel has a diagonal band from 10 ms at 1,000 seconds to 0.5 s at 10,000 seconds."
        },
        {
          "id": "continuity-sync",
          "file": "continuity-sync.png",
          "theme": "data continuity",
          "source": "vitaldb-bench tools/continuity_sync.py",
          "caption": "Gaps (solid) and overlaps (dotted) between consecutive records, as recorded (grey) and on each device's fitted stamp clock (blue): the number at least each size. Where the lines part, a clock correction was showing as a gap or an overlap; where they coincide, the gap is real. The SNUADC's new step at 17.7 ms is the signal each reset loses. Recordings with broken record streams are left out.",
          "alt": "Four log-log panels (SNUADC, BIS, Primus AWP, Primus CO2) of counts against size from 1 ms to 10,000 s. Grey and blue gap lines coincide above about a second in every panel; below it the grey overlap lines for SNUADC, Primus AWP and CO2 fall to a few dozen in blue, and the BIS's grey gap line drops by about two thirds."
        }
      ],
      "derived": {
        "observations": [
          "recording-pauses"
        ],
        "code_urls": {
          "vitaldb-bench/tools/signal_events.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/signal_events.py",
          "vitaldb-bench/tools/signal_events_collect.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/signal_events_collect.py",
          "vitaldb-bench/tools/continuity_figs.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/continuity_figs.py",
          "vitaldb-bench/tools/continuity_sync.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/continuity_sync.py"
        }
      }
    },
    "correction-periods": {
      "id": "correction-periods",
      "name": "How often each device is corrected",
      "summary": "The devices re-synchronise in different ways: SNUADC when its drift reaches about half a second, BIS on a fixed 8,192 s timer, and the Primus by small steps about every two hours that do not keep up with its drift (+104 ppm, measured later by breath matching).",
      "date": "2026-10-17",
      "status": "current",
      "methods": [
        "stamp-reading"
      ],
      "techniques": [
        "sawtooth-fit"
      ],
      "clocks": [
        "snuadc-sample",
        "bis-clock",
        "primus-clock",
        "recorder-pc"
      ],
      "devices": [
        "snuadc",
        "bis",
        "primus"
      ],
      "variables": [
        "snuadc-ecg-ii",
        "bis-eeg",
        "primus-awp"
      ],
      "data": "Record stamps of SNUADC ECG II, BIS EEG1 and Primus AWP in the 250 cases with clock fits.\n",
      "method": "Corrections were taken from the stamp fits. Intervals count only consecutive corrections inside one stretch\nbetween gaps.\n",
      "results": "- **SNUADC (step mode) corrects on a drift threshold.** Its interval follows its rate: about 0.5 s ÷ rate predicts\n  a median of 2.08 h, and 2.06 h is observed (p10–p90 1.70–2.31 h, 182 intervals; the full release gives 2.08 h in 642 recordings, [[study:device-correction-schedules]]).\n- **BIS corrects on a fixed timer** of 8,192 s (2¹³ s) in 95% of 266 intervals. The step takes whatever drift has\n  built up (+83 to +289 ms, median +155 ms); 0.155 s per 8,192 s implies 19 ppm, against 23 ppm from the tooth\n  fits (25 ppm and a median step of +0.17 s in the full release).\n- **Primus AWP shows small corrections** of −64 to +80 ms (median +8 ms) about every 7,230 s (2.0 h; p90 twice\n  that), besides gaps of +20 to +22 s (median 5.5 per case).\n",
      "discussion": "The Primus corrections imply a drift of only about 1–11 ppm against the recorder. Breath matching later measured\nthe drift itself at about +104 ppm ([[parameter:primus-drift-breath]], [[study:ventilator-breath-matching]]):\nthese small corrections do not track the Primus drift, so their size is not its rate.\n",
      "open_questions": [
        {
          "title": "What the Primus's small corrections correct",
          "kind": "phenomenon",
          "summary": "The corrections imply 1–11 ppm, but breath matching measures the Primus drifting about +104 ppm against the recorder PC ([[parameter:primus-drift-breath]]), and the stamps also step at the AWP pauses by amounts unrelated to the drift built up. What the small steps correct, if not the drift, is open. (This replaces the earlier conflict between 1–11 ppm and the assumed 115 ppm, which breath matching settled.)"
        }
      ],
      "code": [
        "vitaldb-bench/tools/clock_correction_periods.py"
      ],
      "derived": {
        "parameters": [
          "primus-drift-implied"
        ],
        "examples": [
          "ppv-by-breath"
        ],
        "code_urls": {
          "vitaldb-bench/tools/clock_correction_periods.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/clock_correction_periods.py"
        }
      }
    },
    "dates-admission-discharge-los": {
      "id": "dates-admission-discharge-los",
      "name": "Admission, discharge and length of stay",
      "summary": "Admission and discharge are dates at 1-day resolution, so length of stay is a derived triangular distribution, and discharge days show a weekly pattern rather than rounding.\n",
      "date": "2026-10-13",
      "status": "current",
      "methods": [
        "clinical-time",
        "value-resolution"
      ],
      "data": "- Admission and discharge dates for 6,388 cases. [[case:1563]] (1900 dates) is excluded.\n",
      "method": "- Checked that discharge minus admission is a whole number of days, and looked at admission lead time and discharge day.\n",
      "results": "- Discharge minus admission is a whole number of days in every case.\n- Most patients were admitted 1 day (41%) or 2 days (38%) before surgery; 171 (2.7%) were admitted on the day of surgery.\n- Median length of stay is 7 days (IQR 4–11). Twenty-six patients went home on the day of surgery.\n- Discharges peak on postoperative day 1 (832) and again on day 7 (669, against 459 on day 6 and 377 on day 8).\n- Thirteen stays longer than 115 days (maximum 223 days) are plausible and kept.\n",
      "discussion": "- The day-7 peak probably reflects a weekly discharge routine. It is a real pattern in the dates, not rounding.\n- No latent fit is needed. Given a recorded difference of n days, the true length of stay lies anywhere from n − 1 to n + 1 days, a triangular distribution peaked at n.\n",
      "limitations": "- The time of day of admission is unknown within the date.\n",
      "tags": [
        "from the resolution work"
      ],
      "derived": {}
    },
    "device-correction-schedules": {
      "id": "device-correction-schedules",
      "name": "When each device's clock is corrected, in every recording",
      "summary": "From the steps between records in all 6,388 recordings: the BIS is corrected every 8,192 s and the Primus every 7,230 s, on their own timers (the first correction comes at any time after the device starts); the SNUADC is reset when 0.49 s of drift has built up. With the full rerun of the correction fit, it gives each device's drift against the recorder PC in 4,728 BIS and 2,728 SNUADC recordings, and shows that the Primus's 20–23 s AWP pauses belong to its correction cycle. Every rate here is relative to the recorder PC, whose own clock drifts and is corrected.",
      "date": "2026-10-19",
      "status": "draft",
      "methods": [
        "data-continuity",
        "stamp-reading"
      ],
      "techniques": [
        "remainder-test"
      ],
      "clocks": [
        "bis-clock",
        "snuadc-sample",
        "primus-clock",
        "recorder-pc"
      ],
      "devices": [
        "bis",
        "snuadc",
        "primus"
      ],
      "variables": [
        "bis-eeg",
        "snuadc-ecg-ii",
        "primus-awp",
        "primus-co2"
      ],
      "code": [
        "vitaldb-bench/tools/correction_schedules.py",
        "vitaldb-bench/tools/step_classify.py",
        "vitaldb-bench/tools/step_classify_agg.py"
      ],
      "data": "The classified steps between records of [[study:step-classification]] (all 6,388 recordings) and the start of\neach track from the segment tables of [[study:continuity-gaps-segments]]; for comparison, the per-recording drift\nrates of [[study:clock-correction-scan]] (BIS 1,627, SNUADC 460 recordings).\n",
      "background": "**Every rate here is relative.** Each record is stamped by the recorder PC, so a device's \"drift\" is its own\nclock against the PC's clock ([[clock:recorder-pc]]), not against true time. The PC clock itself drifts and is\ncorrected by its time service ([[link:recorder-pc-vs-utc]]); a PC correction inside a measurement interval shifts\nthat interval's apparent device drift, so part of the spread below may be the PC's, not the device's.\n\nEarlier studies measured the correction schedules on a few hundred benchmark recordings\n([[study:correction-periods]], [[study:snuadc-stamp-sawtooth]]). The step tables already hold every correction\nin the release, so the schedules, and the drift they imply, can be read for every recording.\n",
      "method": "- **Census:** [[code:vitaldb-bench/tools/clock_correction_periods.py]] rerun on all 6,388 recordings (19\n  October, WSL): the stamp-clock fit of each SNUADC and BIS track, giving a drift for every recording with at\n  least one correction.\n- **BIS:** a correction is a forward step of 10 ms to 1 s in the EEG track that no other device shares. The drift\n  is the step divided by the time since the previous correction, for corrections 8,192 ± 20 s apart.\n- **SNUADC:** a reset is a run of backward steps summing to −0.45 to −0.52 s ([[study:step-classification]]).\n  The drift is its size divided by the time since the previous reset. For the first reset, size divided by the\n  time since the SNUADC track started gives an approximate value. Six recordings with −0.5 s steps throughout\n  are left out.\n- **Primus:** corrections are AWP steps of 10 to 200 ms in either direction; pauses are AWP forward steps of 19\n  to 23.5 s that no other device shares.\n",
      "results": "- **BIS: every 8,192 s, on the BIS's own timer.** 95% of corrections after the first come 8,192 ± 20 s after the\n  previous one; the first comes at any time after the BIS starts (uniform over 0–8,192 s), so the timer does not\n  start with the recording. The median correction is 0.17 s. BIS drift against the recorder PC, in the 1,939\n  recordings with two corrections or more: median 24.8 ppm (5th–95th percentile 7.3–51.1), in several clusters\n  (about 18, 28, 44 and 50 ppm), which [[observation:device-drift-by-setup]] ties to setups. The census fit gives\n  it in all 4,728 BIS recordings with a correction (median 21.1 ppm, 5th–95th 2.1–52.8) and agrees with the\n  intervals where both exist (median difference 0.00 ppm; 33% differ by more than 1 ppm).\n- **SNUADC: reset when 0.49 s of drift has built up.** 3,417 resets in 2,540 recordings: 2,411 as five records\n  out of order ([[observation:reset-records-out-of-order]]), 991 as one step. Between resets, a median 2.08 h\n  (5th–95th percentile 1.65–3.57). Drift in the 642 recordings with two resets or more: median −62.9 ppm\n  (5th–95th −80.3 to −36.9) against the recorder PC, again in clusters, and the same within 0.01 ppm from the\n  census fit. The census gives it in all 2,728 SNUADC recordings with a correction (median −49.5 ppm, 5th–95th\n  −79.1 to −8.1). The earlier per-recording\n  drift ([[study:clock-correction-scan]]) differs by 0.95 ppm in every one of 418 recordings: the stamp\n  arithmetic ([[observation:snuadc-stamp-arithmetic]]), which that fit included.\n- **The SNUADC count usually starts with the recording, but not reliably enough to time drift from it.** The\n  first reset comes at 0.99 × (0.49 s ÷ drift) after the SNUADC starts in the median recording (interquartile\n  range 0.87–1.01), with wide tails (5th–95th 0.56–1.75). Drift timed that way in the 1,898 single-reset\n  recordings is about 10 ppm off the census fit (median difference −9.8 ppm, interquartile −20 to −5), so it is\n  not used.\n- **Drift varies within a recording more than quantisation allows.** Between consecutive intervals of the same\n  recording, BIS drift varies by a median 3.5 ppm (standard deviation; quarter of recordings above 8.6) and\n  SNUADC by 1.2 ppm (above 12.7 in a quarter), against about 1.0 and 0.3 ppm from the stamp resolution. Some of\n  this may be the recorder PC's own corrections.\n- **But the BIS and SNUADC swings move in opposite directions.** In the 133 recordings with at least two\n  intervals of each device, each SNUADC interval's deviation from the recording's mean drift correlates with the\n  time-matched BIS deviation at r = −0.54 (288 intervals; with BIS intervals shuffled within each recording,\n  −0.01, 95% range −0.22 to 0.18). Both drifts are defined the same way (how much the PC's clock gains on the\n  device's), so a PC clock that ran fast for a while would raise both: a positive correlation. The negative one\n  points instead to something acting on the two oscillators in opposite directions (temperature acts on\n  crystals of different cuts with opposite signs), or to a coupling in how they are measured that is not yet\n  found. Within a recording the SNUADC drift swings more than the BIS drift (standard deviation 8.9 against\n  2.6 ppm).\n- **Primus: every 7,230 s, also on its own timer, and the AWP pauses belong to the cycle.** 8,077 corrections in\n  4,529 recordings, each a whole number of 16 ms samples (one sample at 62.5 Hz) in either direction, often two\n  at once (median −16 ms; p10–p90 −144 to +144 ms); 7,230 s apart, or twice that. These steps are far too small\n  for the Primus drift, which breath matching measures at +104 ppm against the PC ([[parameter:primus-drift-breath]]),\n  so they do not give its rate. The 10,496 AWP pauses of 19–23.5 s in 5,502 recordings recur at the same interval, and where a correction lies within 60 s (69% of pauses) it follows the pause by about 21.6 s.\n  The CO2 track has no gap at the same moment in 90% of them, and the AWP data in the pause are lost, not\n  re-stamped: after each pause AWP still lines up with CO2 at the lag it had before ([[observation:primus-awp-pauses]]).\n- These cycles end the long segments: the BIS segments that pile up at 7,500–8,500 s and the Primus AWP segments\n  that never exceed about 7,980 s ([[study:continuity-gaps-segments]]) are cut by the corrections and pauses.\n\n[[figure:correction-schedules]]\n",
      "discussion": "The schedules give drift where earlier studies needed a fit, for three to five times as many recordings, and they\nare consistent with those fits. The BIS and Primus timers run independently of the recording, so a recording\nshorter than one cycle carries no correction of that device; their drift there has to come from the setup\n([[study:setup-identification]]). The SNUADC's threshold behaviour means its first reset also carries drift\ninformation, though less reliably than an interval.\n",
      "limitations": "- Corrections inside a gap or shared with another device's step are not counted; a drift is measured only where\n  two corrections bound an interval.\n- Backward steps were capped at 3,000 per track in the step scan; the few recordings with more lose some resets.\n- All rates are against the recorder PC; none is traceable to UTC ([[link:recorder-pc-vs-utc]] is assumed).\n",
      "open_questions": [
        {
          "title": "Why does the SNUADC appear to speed up when the BIS slows down?",
          "kind": "hypothesis",
          "summary": "Within a recording, the SNUADC and BIS drift deviations against the recorder PC are anticorrelated (r = −0.54, 133 recordings; shuffled null −0.01), the opposite of what a shared PC excursion would give. Candidates: a common factor acting on the two oscillators with opposite signs (temperature, on crystals of different cuts), or a coupling in how the two drifts are measured. Test against each recording's time course (temperature itself is not recorded; operating-room events and recording length are proxies) and in the joint clock model."
        },
        {
          "title": "How much of the drift spread is the recorder PC's?",
          "kind": "next step",
          "summary": "A joint model of the PC and every device clock (each drifting and corrected), fitted to all correction events at once, would separate the PC's corrections from the devices'; events seen by several devices at once (diathermy bursts, [[study:shared-bursts]]) would validate it."
        }
      ],
      "figures": [
        {
          "id": "correction-schedules",
          "file": "correction-schedules.png",
          "theme": "drift and resets",
          "source": "vitaldb-bench tools/correction_schedules.py",
          "caption": "Top: the time between corrections. BIS (left): every 8,192 s; the first after the BIS starts (orange) at any time. SNUADC (middle): the first reset against the time 0.49 s of drift takes at the drift measured from later resets; most lie near the diagonal. Primus (right): corrections and AWP pauses both every 7,230 s. Bottom: BIS drift per recording (1,939); SNUADC drift from reset intervals (642, blue) and from a single reset timed from the start (1,898, orange; about 10 ppm off the census fit, not used); and the time from each AWP pause to the nearest correction. All drifts are against the recorder PC.",
          "alt": "Six panels; a sharp spike at 8,192 s for BIS, a scatter around a diagonal for SNUADC, spikes at 7,230 s for Primus; below, two multimodal drift histograms and a spike at 21 s."
        }
      ],
      "derived": {
        "parameters": [
          "bis-period",
          "bis-step",
          "primus-period",
          "primus-step"
        ],
        "observations": [
          "primus-awp-pauses"
        ],
        "examples": [
          "ppv-by-breath"
        ],
        "code_urls": {
          "vitaldb-bench/tools/correction_schedules.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/correction_schedules.py",
          "vitaldb-bench/tools/step_classify.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/step_classify.py",
          "vitaldb-bench/tools/step_classify_agg.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/step_classify_agg.py"
        }
      }
    },
    "distribution-api-validation": {
      "id": "distribution-api-validation",
      "name": "Package validation and Monte Carlo cache",
      "summary": "The vitaldb_resolution package returns a status for every value in the dataset, reproduces each fitted likelihood, and can precompute every posterior for fast Monte Carlo draws.\n",
      "date": "2026-10-13",
      "status": "current",
      "methods": [
        "value-resolution"
      ],
      "background": "- The API takes a VitalDB variable name and a recorded value and returns a distribution over true values. Single-resolution variables get one uniform component; heaped variables get a mixture over rounding modes.\n",
      "data": "- 6,388 cases × 79 clinical variables (504,652 variable-case cells) and all 928,448 lab results.\n",
      "method": "- Ran the full dataset through the package (`tools/validate.py`) and compared each model's log-likelihood with the stored one.\n- Built quantile tables for posteriors from fitted models and compared knot counts, storage types and knot placement.\n",
      "results": "- Clinical statuses: 217,111 ok, 61,129 zero or not recorded, 45,533 missing, 178,864 not applicable, 1,987 imputed, 20 invalid, 8 top-coded. All lab results are ok, with 19 off-grid flags.\n- The clinical pass takes 7 min and the lab pass 10 min on one core.\n- The cache holds the whole dataset in about 214 MB. One iteration draws every value in about 20 ms for the cases and about 75 ms including all lab results.\n- At any number of knots, int16 has the same worst-case error as float32 and float64. The default is 1,024 knots in int16, with worst mean error 4.7 × 10⁻⁴ SD.\n- Knot placement mattered more than either. End knots at the support ends inflated the SD by up to 80 % at 256 knots; interior knots with tail-mean-preserving end knots cut the worst SD error to 1.1 % and 0.42 %.\n- Since vitaldb-resolution v0.6.0 (18 October; model version 2): each result's density within a rounding\n  interval comes from the fitted latent at call time (uniform only where no latent is fitted), and every result\n  names its density, model id, version and fit hash. The latent and uniform means differ by a few per cent of\n  the interval near the median and by 0.2–0.45 of its width at small values and at the edges. Models are\n  versioned (v1 archived and still loadable, `models.lock` per release), every stored log-likelihood now matches\n  the recomputed one, and the registry records each anchor's basis.\n",
      "discussion": "- The default's worst mean error is about 20 times smaller than the Monte Carlo standard error after 10,000 iterations.\n- Building the package surfaced new rules: two out-of-range anaesthesia ends, 93 clock-offset mismatches, child native resolution, and a revised red-cell rule.\n",
      "limitations": "- Where a posterior has a gap between two rounding windows, draws can land inside it. This is the large point-wise quantile error (0.8 SD at the 99th percentile at 1,024 knots); it barely moves the mean, SD or W1.\n",
      "tags": [
        "from the resolution work"
      ],
      "derived": {
        "examples": [
          "regression-with-rounding"
        ]
      }
    },
    "drift-units": {
      "id": "drift-units",
      "name": "Physical units, shared drift and time order",
      "summary": "Device drift rates cluster into units: about 8 SNUADC boards and 8–18 BIS monitors. Part of the drift is shared by both devices of a case and changes between cases: the recorder PC's own rate. Case order carries no time information.",
      "date": "2026-10-11",
      "status": "current",
      "methods": [
        "stamp-reading"
      ],
      "techniques": [
        "unit-mixture"
      ],
      "clocks": [
        "snuadc-sample",
        "bis-clock",
        "recorder-pc"
      ],
      "devices": [
        "snuadc",
        "bis",
        "vital-recorder"
      ],
      "data": "Per-case drift rates from [[study:clock-correction-scan]]: 460 step-mode SNUADC cases, 1,627 BIS cases, 399 with\nboth, with the serial port each device was attached to.\n",
      "background": "The release names a device by its model and gives no serial numbers, so which physical unit made a recording is\nunknown. A crystal's rate error is a fingerprint, stable to about a ppm, so units can be inferred from the rates.\nEach rate is measured against the PC, so the PC's own rate error adds to every device in a case.\n",
      "method": "Gaussian mixtures with one shared width per unit were fitted to the per-case rates, and a model-selection score\n(BIC) chose the number of units. Rates were grouped by serial port pairs. For setups with both devices, each rate\nwas modelled as the device's own rate plus a term shared by both devices plus noise; a robust covariance\n(minimum covariance determinant) estimated the shared term. Neighbouring case ids were tested for similar drift.\n",
      "results": "- **SNUADC, step mode:** 8 units of width 1.13 ppm, chiefly near −80.4, −72.5, −67.3, −65.2 and −60.4 ppm; 80% of\n  cases are assigned with over 90% confidence.\n- **BIS:** between 8 and 18 units of width about 1.0 ppm; only 40% of cases are confidently assigned.\n- **About 14 recording setups** (pairs of ports) have at least 5 cases; within a port the spread is often only\n  1–2 ppm.\n- **Most of the spread within a setup is shared:** 0.6–1.3 ppm (SD), against under 0.6 ppm for each SNUADC alone\n  and 0.3–1.3 ppm for each BIS. The shared term is fixed within a case (interval-to-interval changes of the two\n  devices are uncorrelated, r = 0.02).\n- **No time order.** Case ids are not chronological: neighbours are no more alike in drift than random pairs.\n  The SNUADC frequent-correction mode is confined to a few setups, can switch either way within a case, and marks\n  a period in at most one setup.\n",
      "discussion": "The shared term is most likely the PC's rate, differing between days rather than wandering during surgery. The\ndifference of two devices' rates cancels it and is the better fingerprint of a device pair.\n",
      "limitations": "Units are inferred, not known; busy ports carried several units.",
      "open_questions": [
        {
          "title": "Which unit is which device",
          "kind": "phenomenon",
          "summary": "Units are inferred from drift alone, and busy ports carried several units. The physical boards behind each unit are not identified."
        },
        {
          "title": "Per-setup drift in the uncertainty models",
          "kind": "next step",
          "summary": "Drift is known per setup far better than over the release ([[observation:device-drift-by-setup]]). The drift parameters now list per-setup values (by PC key), but the clock domains and the planner still use one value for all recordings. They should take a recording's setup when its ports are known, with the release range only as the fallback. The per-setup values come from the correction scan's cases; the whole-release correction steps ([[study:device-correction-schedules]]) would give them for three to five times as many."
        }
      ],
      "code": [
        "vitaldb-bench/tools/syncanalyze.py"
      ],
      "figures": [
        {
          "id": "drift-units",
          "file": "drift-units.png",
          "theme": "drift and resets",
          "source": "ad hoc (no script in vitaldb-bench yet; to be regenerated by one)",
          "caption": "Drift per case falls into discrete units about 1 ppm wide for SNUADC and BIS; paired drifts group by the ports the two devices used.",
          "alt": "Two histograms with fitted Gaussian units, and a scatter coloured by port pair."
        },
        {
          "id": "sync-snuadc-drift-zoom",
          "file": "sync-snuadc-drift-zoom.png",
          "theme": "drift and resets",
          "source": "vitaldb-bench tools/sync_figures.py",
          "caption": "SNUADC drift per case: step-mode recordings sit in clusters near −81, −68 and −60 ppm; small-correction-mode recordings between 0 and +20 ppm.",
          "alt": "Finely binned histogram of drift per case with the step-mode region shaded."
        },
        {
          "id": "sync-drift-within-case",
          "file": "sync-drift-within-case.png",
          "theme": "drift and resets",
          "source": "vitaldb-bench tools/sync_figures.py",
          "caption": "Each correction’s drift agrees with its case’s drift within ±1 ppm about 70% of the time: drift is stable within a case.",
          "alt": "Two histograms of correction drift minus case drift, peaked at zero."
        },
        {
          "id": "sync-drift-pairs",
          "file": "sync-drift-pairs.png",
          "theme": "drift and resets",
          "source": "vitaldb-bench tools/sync_figures.py",
          "caption": "SNUADC and BIS drift measured in the same case form clusters, with SNUADC step-mode and small-correction-mode cases well apart.",
          "alt": "Scatter of BIS drift against SNUADC drift in ppm, coloured by SNUADC mode."
        }
      ],
      "derived": {
        "observations": [
          "case-ids-not-chronological",
          "device-drift-by-setup",
          "snuadc-long-uncorrected",
          "snuadc-mode-setups"
        ],
        "code_urls": {
          "vitaldb-bench/tools/syncanalyze.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/syncanalyze.py"
        }
      }
    },
    "drug-effect-latency": {
      "id": "drug-effect-latency",
      "name": "Drug effects as a clock check",
      "summary": "Infusion pumps, the rapid infuser and the cerebral oximeter share no waveform with the reference. Drug effects that follow infusions at plausible delays (propofol to BIS in about 2 minutes, fluid to CVP in about 30 s) bound their clocks to well within a minute of the recorder.",
      "date": "2026-10-13",
      "status": "current",
      "methods": [
        "shared-events"
      ],
      "techniques": [
        "null-controls"
      ],
      "devices": [
        "orchestra",
        "fms",
        "invos",
        "bis",
        "snuadc"
      ],
      "variables": [
        "pump-rates",
        "fms-infusion",
        "invos-rso2",
        "bis-index"
      ],
      "data": "1,529 cases (1,500 with Orchestra pumps, plus every FMS and Invos case). Every event type was also measured at random times with the same exclusions.",
      "background": "Physiology is a soft link: a drug's onset has a known range, not a known value, so it bounds a clock offset\nrather than measuring it. A response that comes before its cause would expose a clock error.\n",
      "results": "| Event → response | Cases (events) | Responded (controls) | Latency p10 / p50 / p90 (s) | Verdict |\n|---|---|---|---|---|\n| Propofol start → BIS below 60 for 30 s | 497 (499) | 90% | 70 / 132 / 382; none ≤ 0 | Plausible |\n| FMS flow over 100 mL/min → CVP rise ≥ 2 mmHg | 13 (67) | 84% (34%) | 3 / 32 / 129 | Plausible |\n| FMS flow → MAP rise | 13 (68) | 90% (75%) | 2 / 34 / 136 | Weak |\n| Remifentanil up → HR fall ≥ 3 bpm | 1,282 (1,691) | 93% (73%) | 26 / 110 / 274 | Weak |\n| Phenylephrine or norepinephrine step → MAP rise | 27–32 | 63–71% (65–67%) | median 108–109 | Not distinguishable |\n| Invos rSO2 against SpO2 | 30–31 | median r 0.13 | — | Uninformative |\n",
      "discussion": "Propofol to BIS is the strongest check: arm-brain circulation plus effect-site equilibration fits 1–3 minutes, and\nno response precedes its infusion. Vasopressor and remifentanil responses are matched too often at random times to\ntest a clock, and Invos has no usable shared signal.\n",
      "code": [
        "vitaldb-bench/tools/drug_effect_latency.py"
      ],
      "derived": {
        "examples": [
          "bolus-response-latency",
          "cerebral-autoregulation"
        ],
        "code_urls": {
          "vitaldb-bench/tools/drug_effect_latency.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/drug_effect_latency.py"
        }
      }
    },
    "emr-rounding": {
      "id": "emr-rounding",
      "name": "Anaesthesia record times are rounded up",
      "summary": "Anaesthesia start and end entered on a 5-minute mark sit 90–290 s later, against device markers, than entries typed to the minute. Rounding up to the next 5 minutes fits every one of 11 markers better than rounding to the nearest or down.",
      "date": "2026-10-12",
      "status": "current",
      "methods": [
        "clinical-time"
      ],
      "techniques": [
        "null-controls"
      ],
      "clocks": [
        "hospital-records",
        "recorder-pc"
      ],
      "devices": [
        "clinical-records",
        "orchestra",
        "bis",
        "primus",
        "vital-recorder"
      ],
      "variables": [
        "emr-events",
        "pump-rates",
        "bis-index",
        "primus-numerics"
      ],
      "data": "All 6,388 cases.",
      "background": "The clinical table's anaesthesia and operation times come from the electronic anaesthesia record. Some are typed\nto the minute, many fall on 5-minute marks. If 5-minute entries are rounded, the rounding direction decides where\nthe true event lies.\n",
      "method": "Device markers on the recorder clock (propofol and remifentanil infusion start and stop, BIS below 60 or above 80,\nventilation and ETCO2 start and stop, recording end) were compared with the EMR time: D = marker − EMR time. Cases\nwere split by the recovered case clock into 5-minute entries (on an absolute 5-minute mark) and minute entries. The\nminute group's D is the documentation lag; convolving it with each rounding kernel (nearest, floor, ceiling)\npredicts the 5-minute group, scored by kernel-density log-likelihood.\n",
      "results": "- **Every marker agrees in sign:** the 5-minute group's median shifts by −89 to −293 s against the minute group,\n  and all 11 confidence intervals exclude 0. Rounding up predicts −150 s, nearest 0 and floor +150.\n- **Ceiling wins for all 11 markers,** with small margins at anaesthesia start (induction markers spread widely)\n  and large ones at anaesthesia end.\n- **Shifts beyond −150 s** (median about −200 s) suggest 5-minute entries are also written a little later,\n  perhaps retrospectively.\n- **ETCO2 stop against anaesthesia end, as an example:** the 5-minute entries (6,010) sit left of the minute entries\n  (306), close to what rounding up predicts and far from rounding to the nearest or down, and further left still.\n\n[[figure:emr-rounding]]\n",
      "discussion": "Read a 5-minute EMR time t as the interval (t − 300 s, t]: the event is estimated at t − 150 s (SD 87 s), and the\nentry is known no earlier than t plus the documentation lag. Minute entries keep ±60 s. The clinical_time labels in\nthe [[code:timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/connector.py|TimeNet import]] carry these intervals. The extra ~50 s cannot be separated from rounding with these data.\n",
      "limitations": "The two groups may come from different staff or workflows. Rounding is measured for anaesthesia start and end, and assumed for operation start and end.",
      "open_questions": [
        {
          "title": "About 50 s more than rounding",
          "kind": "phenomenon",
          "summary": "The anaesthesia record times sit later than rounding alone explains. Documentation lag and an offset between the EMR clock and the recorder cannot be separated with these data."
        },
        {
          "title": "Rounded up or to the nearest: two studies disagree",
          "kind": "conflict",
          "summary": "For anaesthesia start, the device markers here favour rounding up, but only by a small margin, while the value model, which compares the charted time with the recording start, prefers the nearest mark by 47 BIC points ([[study:value-model-anestart]]). For anaesthesia end the two agree (rounded up). Operation start and end were not measured here; their value models give the nearest mark and rounding down ([[study:value-model-opend]], [[study:timestamp-derived-intervals]]), while the EMR timing entry ([[variable:emr-events]]) treats every 5-minute entry as rounded up. Not reconciled."
        }
      ],
      "code": [
        "vitaldb-bench/tools/emr_markers.py"
      ],
      "references": [
        "rietveld2002rounding"
      ],
      "figures": [
        {
          "id": "emr-rounding",
          "file": "emr-rounding.png",
          "theme": "heaping",
          "source": "vitaldb-bench tools/emr_markers.py",
          "caption": "Left: ETCO2 stop minus the EMR anaesthesia end, for times typed to the minute (grey) and on a 5-minute mark (blue), with the 5-minute group predicted from the minute group under each rounding rule. Right: for all 11 device markers, the shift of the 5-minute group's median against the minute group's, with its bootstrap 95% interval; rounding up predicts −150 s, to the nearest 0 and down +150 s.",
          "alt": "Left, histograms with the blue 5-minute distribution shifted left of the grey minute distribution and closest to the red rounded-up prediction. Right, eleven intervals all left of zero, most around minus 200 s."
        }
      ],
      "derived": {
        "parameters": [
          "emr-rounding"
        ],
        "observations": [
          "emr-offset-confounded",
          "emr-rounded-up"
        ],
        "examples": [
          "bolus-response-latency",
          "induction-hypotension"
        ],
        "code_urls": {
          "vitaldb-bench/tools/emr_markers.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/emr_markers.py"
        }
      }
    },
    "heaping-screen-all-variables": {
      "id": "heaping-screen-all-variables",
      "name": "Heaping screen of all variables",
      "summary": "Every hand-entered clinical value and every lab analyte was screened for mixed resolution and heaping, to decide which need a rounding model and which need only a registry entry.\n",
      "date": "2026-10-13",
      "status": "current",
      "methods": [
        "value-resolution"
      ],
      "background": "- Every clinical-table field labelled \"EMR\" was entered by people. Only the vital-sign tracks were recorded automatically.\n- Variables were ordered by how likely they are to be hand-entered and heaped.\n",
      "data": "- `clinical_data.csv` (6,388 cases) and `lab_data.csv` (34 analytes, 928,448 results), plus the pre-operative copies (`preop_*`).\n",
      "method": "- Clinical values: share of values on each anchor family against the flat expectation, plus an MWI-style index with a binomial threshold.\n- Lab analytes: finest resolution (the finest number of decimal places held by at least 1% of values) and the coarser share. Because results are stored without trailing zeros, about 10% of values should look one step coarser at a single resolution.\n- Whole-number analytes: preference at multiples of 10 and 5, per analyser, with expected counts from a local quadratic fit to the neighbouring values (skipping other multiples of 5). Ratio = observed ÷ expected; z is the Poisson z-score.\n",
      "results": "- Heaped clinical values: urine output (91% end in 0), blood loss (85% multiples of 50), crystalloid (98% multiples of 50), colloid, height (21% whole centimetres, 10% expected) and weight.\n- Age is not heaped: 19.2% end in 0 or 5 (20% expected), and last digits are flat.\n- Red-cell and plasma units, ICU days and length of stay show no heaping.\n- Lab analytes with decimals sit near the 10% coarser share. Exceptions: haematocrit (32.4%, two analysers), total bilirubin (6.5%), lactate (12.5%) and bicarbonate (7.9%).\n- No whole-number analyte shows a preference for multiples of 5 or 10 once the shape of the distribution is allowed for.\n- Sodium and SaO2 only look heaped, because 140, 135 and 95 sit at a peak or on a steep slope. Simulating a smooth peaked distribution gives deviations of the same size at the mode.\n- Total bilirubin's low range (0.2–1.0 mg/dL, 22,240 results) shows no excess at any value: each count is\n  within about 10% of the mean of its neighbours. Its lower coarser share comes from the shape of the\n  distribution.\n- Lactate is measured on the blood-gas analysers only (14,512 of 14,516 results share a timestamp with a pH),\n  so there is no second instrument to compare, and era cannot be tested. No value below 15 is heaped. Its excess\n  is a reporting limit: 317 results (28 cases) are exactly 15.0 mmol/L, against at most 16 for any other value\n  near the top ([[observation:lab-reporting-limits]]).\n- Two more analytes have reporting limits: aPTT at 400.0 s (226 results, 87 cases) and ESR at 2 mm/h (89\n  results, 72 cases).\n- Since vitaldb-resolution v0.6.0 (18 October; model version 2): lactate at 15.0 and aPTT at 400.0 (and pre-\n  operative aPTT) are `top_coded`, ESR at 2 `bottom_coded`, and `p` is described as probably phosphorus\n  (unconfirmed).\n",
      "discussion": "- Apart from computed values, every lab analyte is single resolution per source and needs a registry entry, not a rounding model.\n- Pre-operative copies behave like the lab data: platelets, glucose, AST, ALT and BUN show no heaping; pre-op PT % and sodium show the same artefacts. Pre-op PaO2 is the exception.\n",
      "limitations": "- ESR, ammonia and creatinine clearance have too few results to judge (537–1,415).\n",
      "open_questions": [
        {
          "title": "Analyte p as phosphorus",
          "kind": "hypothesis",
          "summary": "The analyte coded `p` (16,440 results) is missing from `lab_parameters.csv`. Its median of 3.2 (IQR 2.7–3.8, p5–p95 1.9–5.0) fits phosphorus in mg/dL, the only common analyte with that range, but only the VitalDB team can confirm it."
        }
      ],
      "tags": [
        "from the resolution work"
      ],
      "derived": {
        "observations": [
          "lab-reporting-limits"
        ]
      }
    },
    "hr-delay-groups": {
      "id": "hr-delay-groups",
      "name": "Two heart-rate delay groups, one poll apart",
      "summary": "Solar8000 heart rate reaches the recorder either about 1.9 s or about 4.2 s after its window ends: one 2 s poll apart. The group belongs to the recording PC, stays fixed through a case, and on some PCs varies from case to case.",
      "date": "2026-10-13",
      "status": "current",
      "methods": [
        "delays-and-windows"
      ],
      "devices": [
        "solar8000",
        "vital-recorder"
      ],
      "variables": [
        "solar8000-hr"
      ],
      "data": "6,222 cases with heart rate; window fits on 240 cases; a file scan of 657 cases on five ports.",
      "method": "A two-component Gaussian mixture on the per-case HR delays, each case's standard error added to the component\nvariance; a posterior of 0.9 assigns a group. Groups compared with the Solar8000 port, then with the full PC key\n(the ports of the four core devices), department, surgery, rhythm, case order and the HR records themselves.\n",
      "results": "- **Groups at 1.92 s (SD 0.51, 36%) and 4.20 s (SD 0.65, 64%):** 1,775 cases early, 3,652 late, 795 uncertain.\n- **Not a different window:** both groups fit 6–8 s best; the 2.1 s gap matches one Solar8000 record period.\n  The arterial pressure numerics show no such split, so it is specific to HR.\n- **The PC decides, not the port:** ports shared by several PCs separate almost completely by the full device\n  signature (purity 0.90–0.97). On 44 PCs the majority group is right for 94% of cases.\n- **One-sided:** late-majority PCs hold 0.7% early cases (about the misclassification rate); early-majority PCs\n  hold 14% late cases (0–44% per PC).\n- **Stable within a case;** nothing in the records (interval, stamp phase, first-record delay) tells the groups\n  apart.\n",
      "discussion": "Late (one extra 2 s cycle) looks like the default path; some PCs have a faster path that on a given recording\nsometimes does not engage. What sets it is not in the release. The hr_delay_group label gives each case's group,\nand the PC gives a prior for uncertain cases.\n",
      "open_questions": [
        {
          "title": "The recording PC sets the fast path",
          "kind": "hypothesis",
          "summary": "Some recordings get the Solar8000 heart rate one 2 s poll earlier, and on a given recording sometimes do not. The PC key predicts the group for 94% of cases, so the working explanation is the PC's polling path; what makes an early-majority PC deliver late in some recordings is not in the release."
        }
      ],
      "code": [
        "vitaldb-bench/tools/numeric_delay_labels.py",
        "vitaldb-bench/tools/adhoc/hr_group_ports/"
      ],
      "derived": {
        "parameters": [
          "hr-delay-early",
          "hr-delay-late"
        ],
        "observations": [
          "hr-two-delay-groups"
        ],
        "code_urls": {
          "vitaldb-bench/tools/numeric_delay_labels.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/numeric_delay_labels.py",
          "vitaldb-bench/tools/adhoc/hr_group_ports/": "https://github.com/metrology-atlas/vitaldb-bench/tree/main/tools/adhoc/hr_group_ports"
        }
      }
    },
    "instrument-calibration": {
      "id": "instrument-calibration",
      "name": "Instrument accuracy and calibration",
      "summary": "The atlas describes how each value was recorded, but not yet how accurate the instrument behind it was. That needs the calibration and quality-control records of every device, which are not in the release.",
      "date": "2026-10-18",
      "status": "open",
      "methods": [
        "value-resolution"
      ],
      "background": "A recorded value differs from the true one for two reasons: how it was written down (resolution: rounding,\nheaping, sampling) and how well the instrument measured it (accuracy: bias, imprecision, drift between\ncalibrations). The atlas so far covers the first. For accuracy it has, at best, manufacturers' specifications,\nand no variable yet carries an accuracy figure.\n",
      "data": "None from the release. The VitalDB data hold the readings, not the instruments' calibration history.",
      "discussion": "The hospital will have the records that would settle this: laboratory internal quality control and external\nquality assessment for each analyser, biomedical engineering's calibration and maintenance logs for monitors,\npumps, ventilators and scales, and the dates and versions of each device in service. Summarised per device and\nperiod, they would give each variable an accuracy term (a bias and an SD, and how they changed over time) to sit\nbeside its resolution model. Ideally they would be published in the same form as this atlas, so that each\nvariable's uncertainty is complete.\n",
      "limitations": "Until such records are available, every value's uncertainty in the atlas is a lower bound: resolution only.\n",
      "open_questions": [
        {
          "title": "Every instrument within its calibration",
          "kind": "assumption",
          "summary": "The atlas treats each instrument as accurate: no bias, no drift between calibrations, imprecision ignored. The release cannot test this. Calibration and quality-control records for every measuring device used in the VitalDB cases (laboratory analysers, monitors, infusion pumps, ventilators, scales and stadiometers) would replace the assumption with a measured accuracy for each variable. This is beyond the scope of this work, but the hospital will hold the records, and publishing them in a metrology atlas like this one would make every variable's uncertainty complete."
        }
      ],
      "tags": [
        "value side"
      ],
      "derived": {}
    },
    "lab-computed-value-grids": {
      "id": "lab-computed-value-grids",
      "name": "Computed lab values on reachable grids",
      "summary": "PT %, INR and fibrinogen are computed from a finer measurement, so some integers are unreachable and a heaping screen misreads the comb as heaping.\n",
      "date": "2026-10-13",
      "status": "current",
      "methods": [
        "value-resolution"
      ],
      "background": "- PT % and INR are calculated from PT seconds, measured to 0.1 s.\n- Fibrinogen is computed from a clotting time in the same way.\n",
      "data": "- Central-lab PT % (21,143 results screened) and fibrinogen (18,921), from `lab_data.csv`; pre-operative PT % from `clinical_data.csv`.\n",
      "method": "- Whole-number heaping screen at multiples of 10 and 5, with expected counts from a local quadratic fit to neighbouring values.\n- Inspection of which integers occur at all, and how often, against the local average.\n",
      "results": "- PT % shows a ×10 ratio of 1.09 (z 4.0); fibrinogen a ×5 ratio of 1.27 (z 11.3).\n- Each 0.1 s step moves PT % by roughly 1–2 points, so some integers can never occur (98 and 102 almost never appear) and their neighbours carry the extra counts.\n- For fibrinogen, 44 integers between 150 and 450 mg/dL occur at less than 30% of the local average.\n- Pre-operative PT % shows the same artefact.\n",
      "discussion": "- This is not heaping, and fitting a rounding model would mistake it for one.\n- PT % and INR should take their interval from PT seconds (±0.05 s mapped through the local PT–PT % relationship), and use the recorded PT seconds where present.\n- Fibrinogen has no underlying time in the data, so its interval should be the local spacing of reachable values, estimated from the data, rather than ±0.5 mg/dL.\n- The checklist now checks for unreachable values before reading excesses as heaping, and screens use a local smooth expectation, not neighbour means.\n",
      "limitations": "- The fibrinogen interval has to be estimated from the data, because the clotting time is not recorded.\n",
      "tags": [
        "from the resolution work"
      ],
      "derived": {}
    },
    "lab-haematocrit-two-analysers": {
      "id": "lab-haematocrit-two-analysers",
      "name": "Haematocrit from two analysers",
      "summary": "Apparent heaping in haematocrit is two analysers with different resolutions, identifiable record by record from the co-timestamped panel.\n",
      "date": "2026-10-13",
      "status": "current",
      "methods": [
        "value-resolution"
      ],
      "background": "- Lab analytes are machine-reported, so most should show a single resolution.\n- Haematocrit looked like the exception: about a quarter of values are whole numbers, and its coarser share is 32.4% against 10% expected.\n",
      "data": "- 58,489 haematocrit results in `lab_data.csv`, with the source taken from the panel sharing the timestamp (blood-gas panels contain pH).\n- 1,328 pairs where a blood gas precedes a CBC by up to 15 min.\n",
      "method": "- Split results by source before screening: stamped with pH and blood gases means the theatre blood-gas analyser; stamped with Hb, WBC and platelets means the central CBC analyser.\n- Checked the last-digit distribution and heaping at 5 and 10 within each source.\n- Compared resolutions of the other analytes both sources report (glucose, Na, K, iCa, lactate).\n",
      "results": "- CBC analyser (43,939 results): 0.1 % resolution, every decimal digit at 10%, so no rounding.\n- Blood-gas analyser (14,550 results): always whole percent; the last digit is flat (9.4–10.6% each) and counts vary smoothly, so no heaping at 5 or 10.\n- Where a blood gas precedes a CBC by up to 15 min, the blood-gas haematocrit reads 0.8 % lower on average (SD 2.8 %).\n- The other shared analytes have matching resolutions between sources, so only haematocrit needs a source rule.\n",
      "discussion": "- There is no rounding model to fit. The registry gives haematocrit a source-dependent resolution (0.1 % or 1 %, anchor nearest), and the API decides the source from the panel.\n- This is a third kind of mixed resolution, after people writing values in (height) and two scales (weight).\n- The 0.8 % offset is a method difference (conductivity against a counted cell volume), not a resolution issue. It belongs in measurement variability if needed.\n",
      "limitations": "- Nine stand-alone haematocrits (no other analyte at the same time; values 0.0–27) have unknown source. They are probably body-fluid samples and return the two-analyser mixture with flag `stand_alone_probable_body_fluid`.\n",
      "tags": [
        "from the resolution work"
      ],
      "derived": {}
    },
    "lab-preop-pao2-decimal": {
      "id": "lab-preop-pao2-decimal",
      "name": "Pre-operative PaO2 decimal rule",
      "summary": "Pre-operative PaO2 in the clinical table mixes 0.1 and 1 mmHg records, so its resolution is set per record by whether a decimal is present.\n",
      "date": "2026-10-13",
      "status": "current",
      "methods": [
        "value-resolution"
      ],
      "background": "- The pre-operative lab copies (`preop_*` in `clinical_data.csv`) generally behave like the lab data.\n- Pre-operative PaO2 is the exception: it is mixed resolution.\n",
      "data": "- 538 pre-operative PaO2 values.\n",
      "method": "- Screened the share of values ending in .0 against the 10% expected at a single 0.1 mmHg resolution.\n",
      "results": "- 41% end in .0 against 10% expected, so about a third were recorded as whole numbers.\n",
      "discussion": "- This looks like two sources again: probably two blood-gas analysers or a manual transcription step.\n- It is a small field and can take the haematocrit approach: resolution by record, 0.1 or 1 mmHg according to whether a decimal is present.\n",
      "open_questions": [
        {
          "title": "Why some PaO₂ records are whole numbers",
          "kind": "phenomenon",
          "summary": "Which of the two candidate explanations (two analysers or a manual transcription step) is behind the whole- number records is not known."
        }
      ],
      "tags": [
        "from the resolution work"
      ],
      "derived": {}
    },
    "lab-timestamp-meaning": {
      "id": "lab-timestamp-meaning",
      "name": "What the lab timestamp marks",
      "summary": "VitalDB's lab time is a per-specimen timestamp: not the draw, and not the result. Blood gases are stamped 3–12 minutes after the draw, central-lab panels about 8–24 minutes after it, and the result follows a turnaround the release does not record.",
      "date": "2026-10-13",
      "status": "current",
      "methods": [
        "clinical-time",
        "value-resolution"
      ],
      "techniques": [
        "null-controls"
      ],
      "clocks": [
        "hospital-records"
      ],
      "devices": [
        "clinical-records",
        "snuadc"
      ],
      "variables": [
        "lab-blood-gas",
        "lab-central",
        "snuadc-art"
      ],
      "data": "- `lab_data.csv`: 131,942 case-timestamp pairs.\n- 1,440 cases with arterial pressure, for the check against the draw.\n- For the panel comparisons: intra-operative central-lab panels (up to 30 min after anaesthesia end) with a blood\n  gas in the hour before: CBC 1,665, coagulation 1,539, chemistry 1,613.\n",
      "background": "Until 13 October the lab time was treated as the result time, so a lab value counted as known at its timestamp.\nIf it marks something earlier than the result, any model that uses a lab value at its timestamp sees the future.\n\nLab times are machine timestamps at 1 s resolution: the seconds are spread evenly (about 1.7% each) and there is\nno rounding to model. The question is only what event they mark.\n",
      "method": "From the timestamps alone: whether all analytes of a panel share one time, how panels from one draw are spaced\nand in what order, how long after a point-of-care blood gas the central-lab panels are stamped, when in the day\nward samples fall, and whether the minute of the hour heaps. From the draw: each group of lab samples matched to\nthe latest arterial line access before it (within 15 minutes for blood gases, 30 for other panels), against the\nsame lab times shifted 40–80 minutes as the control.\n",
      "results": "- **One timestamp per specimen:** every analyte of a panel shares it to the second.\n- **Not the result time:** panels run on different analysers from one draw are stamped seconds apart (75% of\n  CBC–chemistry pairs within 60 s), centred on zero, and each panel is equally likely to come first. A result\n  time would put chemistry consistently later.\n- **After a blood gas,** central-lab panels are stamped a median of about 10 min later (a quarter within 5 min,\n  three-quarters within 15), the same for CBC, coagulation and chemistry.\n- **Time of day and minute:** ward CBCs peak at 08:00–09:00, after the early-morning phlebotomy round; the minute\n  of the hour varies smoothly, with no spikes at 0 or 5.\n- **Not the draw time:**\n\n| Panel | Sample groups | Matched to an access | Lag from the access start to the timestamp | Above chance |\n|---|---|---|---|---|\n| Blood gas | 5,888 | 4,742 (81%) | p10 3.2 / median 7.2 / p90 12.2 min; modes at 2–6 and 8–12 min | 76% |\n| Central lab | 1,837 | 618 (34%) | excess at 8–18 min, median about 14 min | 23% (weak) |\n",
      "discussion": "Central-lab timestamps mark the specimen reaching or being registered at the lab: the 10 minutes after a blood\ngas and the 8–18 minutes after a line access are the same transport delay seen from two starting points. Blood\ngases seem to take two routes: a point-of-care analyser in theatre (about 4 minutes after the draw) and samples\nsent on (about 9 minutes).\n\nAn earlier write-up from the resolution work (now merged here) put the blood-gas lag at \"a few minutes at most\".\nThe access matching measures it directly, and its figures stand: 3–12 minutes.\n\nPlace a lab value no earlier than its timestamp plus an assumed turnaround; treat the draw as 3–12 minutes (blood\ngas) or roughly 8–30 minutes (central lab) before it. For the value side, lab times have a single resolution\n(1 s, anchor floor). The lab_time labels now carry available_at = lab_time_plus_turnaround, and earlier work\nthat used lab values at their timestamp should be treated as possibly leaky.\n",
      "limitations": "Central-lab matches are mostly chance (many central samples are not drawn from the arterial line), so their lag\nis an estimate. The blood-gas-to-panel lag assumes the gas and the tubes are often drawn together. Result times\nare not in the release, so turnaround cannot be measured.\n",
      "open_questions": [
        {
          "title": "A firmer central-lab lag",
          "kind": "next step",
          "summary": "Most central-lab samples are not drawn from the arterial line, so their access matches are mostly chance and their lag is only an estimate."
        },
        {
          "title": "Turnaround after the timestamp",
          "kind": "assumption",
          "summary": "The release has no result times, so the turnaround from the lab timestamp to the result is assumed, not measured."
        }
      ],
      "code": [
        "vitaldb-bench/tools/lab_draw_labels.py"
      ],
      "figures": [
        {
          "id": "lab-steps-example",
          "file": "lab-steps-example.png",
          "theme": "example traces",
          "source": "vitaldb-bench tools/lab_steps.py",
          "records": [
            3255
          ],
          "caption": "In [[case:3255]] every lab draw time (blue) follows an arterial-line access (orange) by 3–5 minutes; the small panels show the high-pressure flush plateau before each recorded lab time.",
          "alt": "A long timing trace with orange and blue vertical lines above six arterial pressure panels, each with a spike a few minutes before the lab time."
        },
        {
          "id": "line-access-summary",
          "file": "line-access-summary.png",
          "theme": "distributions of measured parameters",
          "source": "ad hoc (no script in vitaldb-bench yet; to be regenerated by one)",
          "caption": "Lab timestamps fall 3–12 minutes after an arterial line access, in two modes; timing steps on the arterial side cluster at accesses.",
          "alt": "Histogram of minutes from access to lab time, and overlaid share histograms against random times."
        }
      ],
      "supersedes": [
        "timestamp-lab-times-meaning"
      ],
      "derived": {
        "parameters": [
          "lab-bg-lag",
          "lab-central-lag"
        ],
        "observations": [
          "lab-time-is-specimen"
        ],
        "examples": [
          "gas-gradients-at-the-draw"
        ],
        "code_urls": {
          "vitaldb-bench/tools/lab_draw_labels.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/lab_draw_labels.py"
        }
      }
    },
    "loop-closure": {
      "id": "loop-closure",
      "name": "Closing loops in the timing graph",
      "summary": "Where two independent routes reach the same clock, the offsets around the loop must sum to zero. Three measured loops close within their noise; the BIS loop closes up to a constant latency; the proposed EMR loop is not a loop at all.",
      "date": "2026-10-13",
      "status": "current",
      "methods": [
        "shared-events"
      ],
      "techniques": [
        "null-controls"
      ],
      "clocks": [
        "snuadc-sample",
        "bis-clock",
        "primus-clock",
        "cardioq-clock",
        "recorder-pc",
        "hospital-records"
      ],
      "devices": [
        "snuadc",
        "bis",
        "primus",
        "cardioq",
        "ev1000",
        "vital-recorder",
        "clinical-records"
      ],
      "variables": [
        "snuadc-art",
        "cardioq-abp",
        "ev1000-art-mbp",
        "bis-eeg",
        "primus-awp",
        "emr-events"
      ],
      "data": "Four loops, each from data measured for another study:\n\n- **Arterial pressure three ways:** the 15 cases with SNUADC, CardioQ and EV1000 arterial pressure; 14 had usable\n  30-minute EV1000 blocks (145 blocks).\n- **BIS EEG:** the burst lags of 145 benchmark cases with BIS ([[study:shared-bursts]]) against both devices'\n  stamps.\n- **Ventilation:** 57 large changes in the breath-matched Primus lag from [[study:ventilator-breath-matching]].\n- **EMR clock and recorder clock:** the EMR times and the recorder EVENT track in all 6,388 cases.\n",
      "background": "The links outward from UTC form a tree. Any further link between two nodes closes a loop, and a loop that does\nnot close means one of its links rests on a wrong assumption. Where two routes meet is where an assumption can be\ntested rather than defended. With several loops, all links can be fitted together as a network adjustment, as in\nsurvey levelling or clock ensembles.\n",
      "method": "For each loop, the offset measured directly between two devices was compared with the sum of the offsets along\nthe other route:\n\n- **Arterial pressure three ways:** EV1000 − SNUADC against (EV1000 − CardioQ) + (CardioQ − SNUADC), per\n  30-minute EV1000 block; EV1000 − CardioQ is measured without SNUADC.\n- **BIS EEG:** the burst lag's ramps, steps and constant against both devices' own stamps.\n- **Ventilation:** jumps in the breath-matched Primus lag against Primus stamp re-anchors; two physiological\n  arms (CVP; systolic pressure) against each other.\n- **EMR clock and recorder clock:** the proposed offset from where entries cluster on 5-minute marks.\n",
      "results": "| Loop | Result | Verdict |\n|---|---|---|\n| Arterial pressure three ways | 14 cases, 145 blocks: residual median +0.13 s (p10–p90 −0.97 to +1.18 s); within 1 s in 75%, 2 s in 96% | Closes, within the CardioQ's re-anchoring spread |\n| BIS EEG | Drift within 25 ppm in 108 of 145 cases (r = 0.88); 79% of lag jumps on a stamp step (chance 12%); a constant −0.80 s (step mode) or −0.69 s (frequent mode; −0.66 s over the whole release) remains | Closes in drift and steps; the constant is a latency |\n| Ventilation | 72% of 57 large lag changes fall on a Primus stamp jump; the physiological arms differ by 37–46 ppm | Partly closes; physiological arms too weak |\n| EMR and recorder | Both arms are measured from the recorder's case start, so any offset cancels; the recorder EVENT track repeats the clinical table's times exactly | Degenerate: not a loop |\n\n**Network adjustment over the whole release (20 October).** Every measured link of every recording is now an edge\nwith a value and a standard error (2,077 recordings; [[code:vitaldb-bench/tools/clock_edges.py]]), and each\nrecording's clock network is adjusted by weighted least squares\n([[code:vitaldb-bench/tools/clock_reconcile.py]], on the dataset-independent [[code:physiosync/physiosync/network.py]]).\nIts fundamental loops give the misclosures:\n\n| Loop | Loops | Misclosure, median (p25–p75) | Share with abs(z) > 3 |\n|---|---|---|---|\n| BIS–SNUADC–PC (BIS steps, ECG–EEG bursts, SNUADC resets) | 253 | +1.3 ppm (−3.7 to +7.8) | 17% |\n| Primus–PC, two routes (breath matching, PIP numerics) | 166 | −2.9 ppm (−48.9 to +46.7) | 5% |\n\nBoth close on average. The variance factors (2 to 5 by method; 1 would mean the stated standard errors are right)\nshow the standard errors are still too small, so single-recording flags are not yet trusted.\n\n[[figure:loop-network-adjustment]]\n",
      "discussion": "The relative uncertainty between two signals depends only on the links between them and their nearest common\nnode; everything above that node is shared and cancels. The BIS constant belongs in the BIS link as a latency.\nThe EMR clock's offset cannot be separated from documentation lag without a time from outside the release.\n",
      "open_questions": [
        {
          "title": "The BIS constant as processing latency",
          "kind": "hypothesis",
          "summary": "The loop closes in drift and steps but leaves −0.80 s (step mode) or −0.69 s (frequent mode; −0.66 s over the whole release). A processing latency in the BIS is the working explanation; it fits the sign and size but has no direct evidence, and it does not explain why the constant differs between modes."
        },
        {
          "title": "The ventilation loop only partly closes",
          "kind": "phenomenon",
          "summary": "The ventilation loop only partly closes: its two physiological arms differ by 37–46 ppm."
        }
      ],
      "code": [
        "vitaldb-bench/tools/loop_closure_art.py",
        "vitaldb-bench/tools/clock_edges.py",
        "vitaldb-bench/tools/clock_reconcile.py",
        "vitaldb-bench/tools/clock_reconcile_figs.py"
      ],
      "figures": [
        {
          "id": "loop-network-adjustment",
          "file": "loop-network-adjustment.png",
          "theme": "population results",
          "source": "vitaldb-bench tools/clock_reconcile_figs.py",
          "caption": "Closing loops in the clock network. (A, B) Misclosures around the BIS and Primus loops. (C) Misclosures over their stated standard errors, against the standard normal expected if those errors were right. (D) Variance factor per method; 1 would mean the stated standard errors are correct.",
          "alt": "Four panels: two histograms of misclosure centred near zero, one histogram of z with a dashed normal curve, and a bar chart of variance factors between 2 and 5.2."
        }
      ],
      "derived": {
        "observations": [
          "eeg-ecg-offset-by-mode",
          "emr-offset-confounded",
          "event-track-generated"
        ],
        "code_urls": {
          "vitaldb-bench/tools/loop_closure_art.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/loop_closure_art.py",
          "vitaldb-bench/tools/clock_edges.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/clock_edges.py",
          "vitaldb-bench/tools/clock_reconcile.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/clock_reconcile.py",
          "vitaldb-bench/tools/clock_reconcile_figs.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/clock_reconcile_figs.py"
        }
      }
    },
    "monitor-lines": {
      "id": "monitor-lines",
      "name": "Monitor clocks in the waveforms' spectral lines",
      "summary": "Differences of the raw converter counts show lines from the monitor's analogue outputs: 240 Hz in arterial pressure and plethysmogram, and a 30 Hz family in the plethysmogram alone. Each measures a monitor clock against SNUADC.",
      "date": "2026-10-17",
      "status": "open",
      "methods": [
        "signal-borne-clocks"
      ],
      "clocks": [
        "monitor-output-clock",
        "oximeter-frame-clock",
        "snuadc-sample"
      ],
      "devices": [
        "solar8000",
        "snuadc"
      ],
      "variables": [
        "snuadc-art",
        "snuadc-pleth",
        "snuadc-ecg-ii"
      ],
      "data": "6 short-to-medium files (1.4–2.6 h), chosen at random; work of the R-peak timing session, consolidated here.",
      "method": "Integer counts were recovered from each track and the spectrum of their sample-to-sample changes searched for\nlines. The 240 Hz line was demodulated in 10 s windows on the global sample index; its frequency came from a\nzero-padded FFT with parabolic peak interpolation. The phase difference of ART and PLETH at that line gave their\nskew.\n",
      "results": "- **240 Hz in PLETH and ART** (weakly in ECG in 3 cases): −25.4 to −28.7 ppm against the SNUADC.\n- **A 30 Hz family (30, 60, 90, 150 Hz) in PLETH only:** −30 to −73 ppm in frequency, that is +30 to +73 ppm in\n  period, matching the oximeter frame clock from fiducial locking ([[study:ppg-frame-clock]]).\n- **ART leads PLETH by 0.31–0.39 ms** at 240 Hz in every case, consistent with the converter reading its channels\n  one after another.\n- **Line frequencies group files by device:** two of the six share every line and are probably the same monitor\n  and converter.\n- **No frame-slip sawtooth was seen** in the ART→PLETH lag of these six files, which conflicts with\n  [[study:ppg-sawtooth]]; a few slips per short file barely show in a jump histogram, and many stretches have a\n  paused sawtooth. To settle by running these files through [[software:physiosync]].\n",
      "discussion": "A third, independent clock (the monitor's output stage) measured from the waveforms alone, and a blind way to\ngroup recordings by device.\n",
      "open_questions": [
        {
          "title": "Monitor clock lines in six files only",
          "kind": "next step",
          "summary": "The monitor clock lines were seen in six files; running these files through physiosync, and then a larger set, would settle whether they are general."
        }
      ],
      "limitations": "Six files.",
      "derived": {
        "parameters": [
          "monitor-240hz-rate"
        ],
        "observations": [
          "pleth-30hz-frames",
          "snuadc-channel-skew"
        ]
      }
    },
    "numeric-clock-check": {
      "id": "numeric-clock-check",
      "name": "SNUADC against the PC-stamped heart rate",
      "summary": "The Solar8000 heart rate is stamped by the recorder on arrival, so the delay from the ECG to it, on the ECG's raw sample count, ramps at the SNUADC clock's rate: −65.3 ppm, against −66.7 ppm from the stamp model. Two independent routes agree.",
      "date": "2026-10-17",
      "status": "current",
      "methods": [
        "shared-events"
      ],
      "clocks": [
        "snuadc-sample",
        "recorder-pc"
      ],
      "devices": [
        "snuadc",
        "solar8000"
      ],
      "variables": [
        "snuadc-ecg-ii",
        "solar8000-hr"
      ],
      "data": "66 step-mode cases without gaps (722 thirty-minute blocks); 7 frequent-correction cases without gaps.",
      "method": "The delay from the ECG to the Solar8000 HR was measured in 30-minute blocks with the ECG on its raw sample count\nand, separately, on the stamp-model line.\n",
      "results": "- **On the sample count the delay ramps at −65.3 ± 3.9 ppm;** the stamp model gives −66.7 ppm median for the same\n  cases.\n- **On the corrected line the ramp is −13.0 ± 4.0 ppm:** small, not quite zero. A residual to watch.\n- **Frequent-correction cases are inconclusive** here (7 cases, 52 ± 40 ppm).\n- **Per-case precision is poor:** block delays scatter by about 0.7 s, so this works pooled, not per case.\n",
      "discussion": "A second, independent measure of the SNUADC–PC rate, through a device with no clock of its own.\n",
      "open_questions": [
        {
          "title": "A residual of −13 ppm",
          "kind": "phenomenon",
          "summary": "On the corrected SNUADC line the ECG-to-HR delay still ramps by −13.0 ± 4.0 ppm. It may be the Solar8000's own processing, an error in the stamp model's rate, or the PC; a larger run (all step-mode cases without gaps) would narrow it. The whole-release run ([[study:solar-snuadc-clock]]) cannot settle it: per recording it is too noisy, and in recordings under about 4 h its rate is biased toward zero."
        },
        {
          "title": "Frequent-correction mode on more cases",
          "kind": "next step",
          "summary": "Frequent-correction mode is inconclusive on 7 cases (52 ± 40 ppm)."
        }
      ],
      "code": [
        "vitaldb-bench/tools/numeric_clock_check.py"
      ],
      "derived": {
        "code_urls": {
          "vitaldb-bench/tools/numeric_clock_check.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/numeric_clock_check.py"
        }
      }
    },
    "numeric-delay-variability": {
      "id": "numeric-delay-variability",
      "name": "Numeric delays are fixed within a case, different between cases",
      "summary": "With each numeric's algorithm held fixed, delays were fitted for every case and every 30-minute block. They do not drift within a case, but 40–70% of their variance lies between cases, so a single population delay is wrong by up to 2 s for HR and 1 s for the others. Each case gets its own delay label.",
      "date": "2026-10-12",
      "status": "current",
      "methods": [
        "delays-and-windows"
      ],
      "devices": [
        "solar8000",
        "primus"
      ],
      "variables": [
        "solar8000-hr",
        "solar8000-art-numerics",
        "primus-numerics"
      ],
      "data": "900 random cases with SNUADC and Solar8000, then 6,387 of 6,388 cases (one file too large for the analysis machine); Primus ETCO2 and PIP refitted in all 6,199 Primus cases on 13 October.",
      "method": "The pilot's consensus algorithm for each track; the delay fitted per case and per 30-minute block with the same\nuniform-phase likelihood; variance split by a one-way random-effects model on the block delays (between-case SD,\nwithin-case SD, intraclass correlation); case delays compared with set-up variables and across tracks.\n",
      "results": "| Track | Delay from window end, p10 / p50 / p90 (s) | Between-case SD (s) | ICC |\n|---|---|---|---|\n| HR | 1.7 / 3.7 / 5.0 | 1.31 | 0.71 |\n| ART systolic | 0.3 / 1.0 / 1.9 | 0.79 | 0.61 |\n| ART mean | 1.0 / 1.7 / 2.5 | 0.78 | 0.61 |\n| ART diastolic | 0.3 / 1.2 / 2.0 | 0.87 | 0.68 |\n| CVP | 3.1 / 3.8 / 4.7 | 0.60 | 0.41 |\n| Primus ETCO2 | 0.75 / 1.55 / 3.4 | 0.94 | 0.58 |\n| Primus PIP | 3.65 / 4.6 / 6.5 | 0.96 | 0.55 |\n| Primus RR | 1.6 / 4.3 / 7.6 | 1.98 | 0.67 |\n\n- **No drift within cases** (median 0 s/h).\n- **Shared offsets within a device family:** the three arterial pressure delays correlate at ρ 0.86–0.92; across\n  families hardly at all.\n- **HR is the only track whose delay follows the Solar8000 port** (η² 0.35): two groups\n  ([[study:hr-delay-groups]]).\n- **The first Primus fit gave ETCO2 a delay of −1.5 s,** which the availability rule forbids: the hold until the\n  next breath had been counted twice. Refitted with a one-record phase, every median delay is positive.\n",
      "discussion": "One delay per case is enough; per-record labels are not needed. Per-case labels (numeric_delay) carry the delay,\nits standard error (median 0.15–0.29 s, 1.0 s for RR) and a quality flag; 607 per-case delays that still come out\nbelow zero are flagged weak. The population distribution is the prior where a case cannot be fitted.\n",
      "code": [
        "vitaldb-bench/tools/numeric_delay_variability.py",
        "vitaldb-bench/tools/numeric_delay_labels.py"
      ],
      "figures": [
        {
          "id": "numeric-delay-variability",
          "file": "numeric-delay-variability.png",
          "theme": "delay and window fits",
          "source": "vitaldb-bench tools/numeric_delay_variability_plot.py",
          "caption": "Delays differ between cases far more than a fixed delay would allow (first run, 879 cases; a 6,222-case version is to replace it).",
          "alt": "Paired range bars, observed against expected, per numeric."
        }
      ],
      "derived": {
        "parameters": [
          "art-mbp-delay",
          "cvp-delay",
          "etco2-delay",
          "pip-delay",
          "rr-delay",
          "solar-delay-drift"
        ],
        "observations": [
          "numeric-delays-per-case",
          "numerics-not-front-stamped"
        ],
        "examples": [
          "hypotension-burden"
        ],
        "code_urls": {
          "vitaldb-bench/tools/numeric_delay_variability.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/numeric_delay_variability.py",
          "vitaldb-bench/tools/numeric_delay_labels.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/numeric_delay_labels.py"
        }
      }
    },
    "numeric-derivation-pilot": {
      "id": "numeric-derivation-pilot",
      "name": "How the monitors compute their numbers",
      "summary": "Recomputing each monitor numeric from its own waveform finds a short, simple algorithm for every one (a median, an exponential average or a time mean over 3–30 s) and a delay of 2–9 s behind the newest data it uses. No value is stamped before its data exist.",
      "date": "2026-10-12",
      "status": "current",
      "methods": [
        "delays-and-windows"
      ],
      "devices": [
        "solar8000",
        "primus",
        "bis"
      ],
      "variables": [
        "solar8000-hr",
        "solar8000-art-numerics",
        "solar8000-pleth-hr",
        "primus-numerics",
        "snuadc-ecg-ii",
        "snuadc-art",
        "primus-co2",
        "primus-awp"
      ],
      "data": "Record rates on 1,000 random cases. The algorithm fit on 24 cases with ECG, arterial pressure, CVP and Primus\nwaveforms; PLETH_HR on 231 cases.\n",
      "background": "A numeric summarises a window of signal that ended before its stamp. Knowing the window and the delay lets a value\nbe placed against the physiology it describes, and tells a model when the value could first have been known.\n",
      "method": "**Timing model.** A value stamped at t is computed at u = t − d − φ from the data in a window ending at u: d is the\nprocessing and transmission delay, and φ is where within the device's update phase the computation fell (uniform\nover one poll for values the monitor recomputes on its own schedule; over one 0.48 s record for Primus values of\nthe last completed breath). Values are uniform over their reporting bin (rounded or truncated). Every delay is\nmeasured from the window's end, the anchor; the window's middle is a derived description only.\n\n**Fit.** Each numeric was fitted against beat-by-beat or breath-by-breath values from its waveform. Candidates: a\nmean, median or exponential average over N events or T seconds, rate and waveform-mean variants, delays from −3 to\n15 s; the best by likelihood per case. A held-value model (refreshing every 4–12 s) was the alternative.\n",
      "results": "**Record rates:** Solar8000 every 2.00 s (99% within ±50 ms), BIS every 1.00 s, Primus about every 7.0 s (or\n6.3 s; ±0.4 s, not locked to breaths). Solar8000's ETCO2 and ventilator values change only every 6–8 s: Primus\nvalues relayed through the Solar8000. Every Solar8000 ST_II record is written twice at the same stamp.\n\n| Numeric | Best fit (cases) | Delay from window end |\n|---|---|---|\n| HR | median R–R over ~6 s, truncated (14/24) | 3.6 s |\n| ART systolic | median over 3 s, or EWMA ½ | 2.1 s |\n| ART mean | EWMA ¼ per beat (16/24) | 2.3 s |\n| ART diastolic | EWMA ½ (20/24) | 1.9 s |\n| CVP | waveform mean over 4–5 s (16/21) | 5.0 s |\n| PLETH_HR | median of ~12 beats (134/231) | 3.4 s |\n| Primus RR | 60 / mean breath interval over 30 s (17/24) | 8.5 s |\n| Primus ETCO2, PEEP | the latest breath | 1.8–1.9 s |\n| Primus PIP | the latest breath | 5.2 s |\n\n- **No slow updates:** the held-value model lost in every case for HR, ART, CVP and PLETH_HR.\n- **HR is truncated, not rounded** (24/24); RR clearly rounds.\n- **No front-stamping:** no numeric is stamped before the data it describes.\n",
      "discussion": "Mean against median, or beats against seconds, often differ by only 0.01–0.05 log-likelihood per record, so the\nalgorithm family is less certain than the delay. Pressure numerics sit about 4 mmHg below the analogue waveform\nin these cases (scale, not timing); over the whole release the median is −1.2 mmHg, varying by about ±6 mmHg\nbetween patients ([[example:hypotension-burden]]). Delays here are pilot values; [[study:numeric-delay-variability]] measures them in every case.\n",
      "code": [
        "vitaldb-bench/tools/numeric_intervals.py",
        "vitaldb-bench/tools/numeric_derivation_pilot.py"
      ],
      "figures": [
        {
          "id": "numeric-intervals-jitter",
          "file": "numeric-intervals-jitter.png",
          "theme": "distributions of measured parameters",
          "source": "vitaldb-bench tools/numeric_intervals_plot.py",
          "caption": "Record intervals cluster tightly around each device’s nominal period, within a few milliseconds for Solar8000 and BIS.",
          "alt": "Four histograms of record interval in 5 ms bins."
        },
        {
          "id": "numeric-intervals-dist",
          "file": "numeric-intervals-dist.png",
          "theme": "distributions of measured parameters",
          "source": "vitaldb-bench tools/numeric_intervals_plot.py",
          "caption": "Time between records and between value changes for each numeric track in 1,000 random cases: Solar8000 records every 2 s, Primus about every 7 s, BIS every 1 s.",
          "alt": "Grid of histograms on a log time axis, one per track, grey for record intervals and coloured for change intervals."
        },
        {
          "id": "numeric-window-diagram",
          "file": "numeric-window-diagram.png",
          "theme": "schematics",
          "source": "vitaldb-bench tools/numeric_window_diagram.py",
          "caption": "How one recorded numeric value relates to its data window: window length T, processing delay d and poll phase φ.",
          "alt": "Schematic ECG with an R–R window, brackets and dimension arrows on a time axis relative to the stamp."
        },
        {
          "id": "numeric-derivation-pilot",
          "file": "numeric-derivation-pilot.png",
          "theme": "delay and window fits",
          "source": "vitaldb-bench tools/numeric_derivation_plot.py",
          "caption": "In a 24-case pilot each monitor numeric is best explained by a short median, exponential or latest-breath window, recorded 2–9 s after its data. Held values repeat even when a large change is predicted.",
          "alt": "Three panels: delay dot and interquartile range per numeric, the best-fitting algorithms, and repeat fraction against predicted change."
        }
      ],
      "derived": {
        "parameters": [
          "hr-window"
        ],
        "observations": [
          "hr-truncated",
          "numerics-not-front-stamped",
          "solar-relays-primus",
          "solar-st-duplicated"
        ],
        "examples": [
          "cuff-vs-arterial",
          "hypotension-burden"
        ],
        "code_urls": {
          "vitaldb-bench/tools/numeric_intervals.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/numeric_intervals.py",
          "vitaldb-bench/tools/numeric_derivation_pilot.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/numeric_derivation_pilot.py"
        }
      }
    },
    "openeeg-bis-mimic": {
      "id": "openeeg-bis-mimic",
      "name": "What a BIS-mimic project says about the index's smoothing",
      "summary": "The openeeg project reproduces the BIS index from the raw EEG and infers each case's smoothing setting: about 80% of its cases come out at 15 s. Its alignment cannot serve as a timing reference.",
      "date": "2026-10-13",
      "status": "current",
      "techniques": [
        "prediction-test"
      ],
      "methods": [
        "source-review",
        "delays-and-windows"
      ],
      "devices": [
        "bis"
      ],
      "variables": [
        "bis-index",
        "bis-eeg"
      ],
      "data": "No VitalDB data were analysed here: the openeeg repository (commit bd476c5, 2 June 2026), its code and\ndocumentation, were read. The figures quoted (about 80% of its cases at the 15 s smoothing setting) are the\nproject's own, from its VitalDB cases.\n",
      "background": "The BIS index is the monitor's EEG features smoothed over a window the operator chooses, and VitalDB does not record\nthe setting.\n",
      "method": "The project's code and documentation were read for how it infers the smoothing and how it aligns index and EEG.\n",
      "results": "- **Smoothing setting:** inferred per case by smoothing its own unsmoothed prediction with each candidate and keeping\n  the closest match to the recorded index; about 80% of cases come out at 15 s. Modelled as an exponential\n  average, the index then trails the EEG by about (W − 1)/2 s: about 7 s at 15 s and 15 s at 30 s, plus processing.\n- **Not a timing reference,** for three reasons: each 1 s feature row is paired with the index at the same second\n  while the EEG behind it covers 2–4 s later; the EEG is loaded onto a fixed 1/128 s grid that absorbs the BIS stamp\n  corrections; and the trained model's inputs include trailing 5–60 s means of its own score, so its output carries\n  an unknown lag.\n",
      "discussion": "The atlas's own fit ([[study:bis-index-delay]]) uses no trained model and finds the 15 s setting in about half the\ngood fits. openeeg says nothing about device clocks, so the BIS clock link is unchanged.\n",
      "references": [
        "openeeg"
      ],
      "derived": {
        "observations": [
          "bis-index-slow"
        ]
      }
    },
    "paced-ecg": {
      "id": "paced-ecg",
      "name": "Paced ECG as an independent clock",
      "summary": "Fixed-rate pacing gives a clock the recording chain cannot touch. Timing paced beats on three timelines shows that each SNUADC step-mode correction loses about 18 ms of signal, and that nothing else breaks the sample stream.",
      "date": "2026-10-17",
      "status": "current",
      "methods": [
        "signal-borne-clocks"
      ],
      "techniques": [
        "template-beat-timing",
        "phase-step-detection"
      ],
      "clocks": [
        "pacemaker",
        "snuadc-sample",
        "recorder-pc"
      ],
      "devices": [
        "pacemaker",
        "snuadc",
        "vital-recorder"
      ],
      "variables": [
        "snuadc-ecg-ii"
      ],
      "data": "All 6,388 cases were screened for runs of beats at one constant interval. Seven pass, paced at about 1,204, 1,000\nand 860 ms (50, 60 and 70 bpm); only two of them list a pacemaker in the pre-operative ECG field. About 40 more\ncases have long regular runs at 370–920 ms, but their interval wanders by about 1 ms between stretches: regular\nsinus rhythm, not pacing.\n",
      "background": "Every clock in the recording chain is either corrected by the recorder or measured against a clock that is. A\npacemaker at a fixed rate is different: beat *n* falls at *t₀ + n·P* on its own crystal, whatever the recorder\ndoes. Any break in a timeline shows as a phase step in the pacing train on that timeline.\n",
      "method": "Each beat was timed by template matching (fit residual about 1 ms) and placed on three timelines: the sample\ncount, the record stamps, and the stamp-model line. Between stretches paced at the same interval *P*, a phase step\non a timeline marks a break in that timeline. Steps of 6 ms or more were examined one by one, and an intervening\npoorly matched beat (an intrinsic or fusion beat, which resets a demand pacemaker) was taken as an explanation.\n",
      "results": "- **The pacing intervals agree between stretches** to 0.05–0.1 ms on the sample count.\n- **Each step-mode correction loses about 18 ms of signal.** At the three corrections inside paced rhythm (two\n  cases, both leads where available), the pacing train steps on the sample count by −17.2 to −18.2 ms. The record\n  stamps step by −480.6 to −481.0 ms, and the train on stamp time by −498.1 to −498.8 ms. So the true correction\n  is about 0.498 s, close to a 0.5 s threshold, and the stamps show only 0.48 s of it because about 9 samples are\n  never taken. Every record still holds 100 samples, so nothing in the file shows the loss.\n- **Recording gaps of 10–18 s shift the stamps by 127–149 ms** against the pacing train (four gaps, two cases).\n- **No other breaks.** Apart from corrections and gaps, no phase step of 6 ms or more went without an intervening\n  beat or a poorly matched beat. No hidden concatenation was found in the paced stretches.\n",
      "discussion": "The −0.48 s correction quoted for step mode is the visible part of a 0.5 s resynchronisation. Without the loss,\nevery sample after a correction sits about 18 ms early, adding up over a case's one to three corrections. The\nECG–EEG burst lag confirms the sign without pacing: it steps by +13.0 ± 1.7 ms at each correction (93 cases),\na little under the direct measurement.\n\nThe stamp model now adds the loss back at each correction, which removes the burst-lag step (−3.3 ± 1.8 ms after\nthe fix) but barely moves the overall error, because the loss is small beside the window-to-window spread of the\nburst lag. The slight overshoot hints that the average loss is nearer 15 ms.\n",
      "limitations": "Three paced corrections in two cases. The gap shift is measured modulo one pacing period. The location of the\nlost time was not resolved here; [[study:reset-cluster-order]] later placed it inside the last old-grid record.\n",
      "open_questions": [
        {
          "title": "Long regular runs that may be paced",
          "kind": "phenomenon",
          "summary": "About 40 cases have long regular runs (370–920 ms) whose interval wanders by about 1 ms between stretches. Whether they are paced is not settled."
        }
      ],
      "code": [
        "vitaldb-bench/tools/paced_screen.py",
        "vitaldb-bench/tools/paced_breaks.py"
      ],
      "derived": {
        "parameters": [
          "pacemaker-interval-agreement",
          "snuadc-gap-stamp-shift",
          "snuadc-reset-loss",
          "snuadc-step-correction-size"
        ],
        "observations": [
          "reset-signal-lost",
          "snuadc-stamp-arithmetic"
        ],
        "code_urls": {
          "vitaldb-bench/tools/paced_screen.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/paced_screen.py",
          "vitaldb-bench/tools/paced_breaks.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/paced_breaks.py"
        }
      }
    },
    "pat-offset-census": {
      "id": "pat-offset-census",
      "name": "One device delay for the PLETH across the release",
      "summary": "The stored ECG-to-PLETH delay was measured in 5,536 recordings, to the foot and to the steepest upstroke of each pulse. It has one peak (655 ms to the foot, 715 ms to the upstroke, in the median recording), no clusters by recording setup or SNUADC port, and no shift along the case order: one global device delay, 385 ms with an adopted R-to-foot finger PAT of 270 ms, fits the whole release, as Ruffolo et al. found in their own data.",
      "date": "2026-10-18",
      "status": "current",
      "methods": [
        "delays-and-windows",
        "signal-borne-clocks"
      ],
      "techniques": [
        "beat-matching"
      ],
      "clocks": [
        "oximeter-frame-clock"
      ],
      "devices": [
        "snuadc",
        "solar8000"
      ],
      "variables": [
        "snuadc-pleth",
        "snuadc-ecg-ii",
        "snuadc-art"
      ],
      "code": [
        "vitaldb-bench/tools/pat_offset_census.py"
      ],
      "data": "Every recording with ECG lead II and PLETH at 500 Hz (6,136 of 6,388); 5,536 passed the quality rule below, 2,900\nof them with an arterial line. Setups are the PC keys of [[study:setup-identification]].\n",
      "background": "The PLETH reaches the recorder about 0.4 s late ([[observation:ppg-hardware-delay]]). The PAT example first removed\neach case's own offset, which also removed real differences in PAT between patients. Whether one delay fits every\nrecording, or one per setup or per epoch, decides how the delay can be removed without that loss.\n[[reference:ruffolo2025high]] saw one global offset (two, over two epochs) in their own monitoring system.\n",
      "method": "- Per recording, 8 segments of 2 minutes spread over the ECG–PLETH overlap from 10 minutes in. In each: R peaks,\n  and per pulse in the PLETH (and in the arterial pressure where there is a line) the steepest upstroke and the\n  foot (intersecting tangent: where the tangent at the steepest upstroke meets the level of the preceding minimum).\n- **Pairing by the modal delay.** The stored delay (about 0.7 s) is longer than one RR interval at fast heart\n  rates, so a nearest-after search pairs pulses one beat out. All R-to-upstroke lags within 0.1–1.2 s are\n  histogrammed per segment; a constant delay makes one sharp peak, while pairings one beat out move with the RR\n  interval and smear. Each R peak then takes the upstroke nearest the mode.\n- A segment counts if at least half of all lags lie within 20 ms of the mode and the per-beat spread (IQR) is at\n  most 30 ms; a recording needs 3 such segments. The per-case value is the median over its segments.\n- Finger minus arterial PAT, beat by beat, removes most of the patient's own PAT, so its spread between recordings\n  shows the device delay's structure far more sharply than the finger PAT alone.\n",
      "results": "- **One peak.** Stored ECG→PLETH delay to the steepest upstroke: 715 ms in the median recording (interquartile range 699–730 ms). Finger\n  minus arterial PAT: 497 ms (484–510 ms; 5th–95th percentile 456–532 ms). Neither has a second cluster.\n- **The setup explains almost nothing:** 4% of the variance of finger minus arterial PAT between recordings (PC\n  key, 57 keys) and 2% (SNUADC port). Every one of the 31 keys with at least 20 recordings has its median within\n  about ±15 ms of the global value, and each key's own spread is wider than the gaps between keys. The earlier\n  per-setup delays (368–399 ms, from 293 cases) were mostly sampling noise.\n- **No epochs along the case order.** In 16 bins of 400 cases the median moves by at most 4 ms.\n- **Stable within a recording:** from the first good segment to the last (2 hours apart in the median case) the\n  stored delay changes by −4 ms (interquartile range −24 to +16).\n- **The stored delay keeps the patient's PAT:** per case, stored finger PAT correlates with arterial PAT (Spearman\n  0.56). Removing one global delay keeps that; removing each case's own offset destroys it.\n- **Foot and upstroke.** To the foot, the stored delay is 655 ms (interquartile range 640–670 ms), with the same\n  single peak and the same absence of setup clusters (setup explains 2% of its variance) and of a trend along the\n  case order (bin medians within 4 ms). The PLETH's foot-to-upstroke time is 59 ms (56.7–62.0 ms) and barely varies.\n  The arterial line: 178 ms to the foot, 216 ms to the upstroke. Finger minus arterial PAT is 475 ms foot to foot.\n- **The global delay:** the adopted 270 ms ([[study:ppg-offset-reference]]) is an R-to-finger-foot value, so the\n  delay is taken at the foot: 655 − 270 = **385 ms**, which agrees with the earlier foot-based estimate over 293\n  cases (385 ms). It implies a finger-minus-radial difference of about 90 ms foot to foot. (A first version used the\n  upstroke, 715 − 270 = 445 ms, which mixed two fiducials.)\n\n[[figure:pat-offset-census]]\n",
      "discussion": "One delay fits the release, so PAT can be compared between patients after subtracting it. Only the absolute level\nrests on an outside value, the adopted finger PAT. Case order is only a proxy for time (the release has no dates),\nso an epoch change like Ruffolo et al.'s would show here only if case numbers follow recording order.\n",
      "limitations": "- 600 recordings failed the quality rule, mostly at fast heart rates or with noisy ECG; if their delay differs,\n  the census would miss it.\n- The foot is found on the 0.5–10 Hz band-passed pulse, which rounds the corner where the pulse starts to rise;\n  a foot on the raw signal might sit a few milliseconds earlier.\n- The sawtooth adds up to one frame (33 ms) to the stored delay; averaged over a recording it is about the same in\n  every case and is part of the global value.\n",
      "open_questions": [
        {
          "title": "Is 90 ms from radial artery to finger plausible?",
          "kind": "assumption",
          "summary": "With the global 385 ms, finger PAT exceeds radial-artery PAT by about 90 ms foot to foot. Published values for the radial-to-fingertip pulse transit (and for the volume pulse lagging the pressure pulse) would check the adopted 270 ms independently; a larger or smaller gap there would move the global delay by the same amount."
        },
        {
          "title": "Epochs in time",
          "kind": "next step",
          "summary": "Ruffolo et al. saw two offsets over two epochs. VitalDB has no recording dates; any other ordering of the cases in time (software versions in the file headers, say) would allow a sharper test than the case number."
        },
        {
          "title": "Recordings that failed the quality rule",
          "kind": "next step",
          "summary": "Fast heart rates and noisy ECG make the modal delay ambiguous. A beat-to-beat pairing that uses the ECG's RR interval explicitly would bring most of the 600 back into the census."
        }
      ],
      "references": [
        "ruffolo2025high"
      ],
      "figures": [
        {
          "id": "pat-offset-census",
          "file": "pat-offset-census.png",
          "theme": "delay and window fits",
          "source": "vitaldb-bench tools/pat_offset_census.py",
          "caption": "Left: per-recording median of the stored ECG→PLETH delay to the steepest upstroke (grey, 5,536 recordings) and of finger minus arterial PAT (blue, 2,900 with an arterial line); one peak each. Middle: finger minus arterial PAT per PC key (median and interquartile range, 31 keys with at least 20 recordings) against the global 497 ms (±15 ms shaded). Right: bin medians along the case order (bins of 400 cases) minus the overall median.",
          "alt": "Left, two single-peaked histograms near 497 and 715 ms. Middle, 31 short intervals all centred within 15 ms of a red line at 497 ms. Right, two flat lines within a few milliseconds of zero."
        }
      ],
      "derived": {
        "parameters": [
          "pleth-chain-delay"
        ],
        "observations": [
          "ppg-hardware-delay"
        ],
        "examples": [
          "pat-vs-bp"
        ],
        "code_urls": {
          "vitaldb-bench/tools/pat_offset_census.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/pat_offset_census.py"
        }
      }
    },
    "ppg-art-reference": {
      "id": "ppg-art-reference",
      "name": "Arterial pressure as a check on the PPG correction",
      "summary": "Arterial pressure is sampled by the same converter but has no frame and no sawtooth, so every PPG slip leaves ECG→ABP unchanged. Against it, detected slips are 97–100% real and tier A leaves about 2 ms of drift mid-tooth.",
      "date": "2026-10-11",
      "status": "current",
      "methods": [
        "signal-borne-clocks",
        "shared-events"
      ],
      "techniques": [
        "beat-matching",
        "null-controls"
      ],
      "clocks": [
        "oximeter-frame-clock",
        "snuadc-sample"
      ],
      "devices": [
        "snuadc",
        "solar8000"
      ],
      "variables": [
        "snuadc-pleth",
        "snuadc-art",
        "snuadc-ecg-ii"
      ],
      "data": "201 cases with an arterial line; an injected-sawtooth benchmark on 28 of them (84 runs).",
      "method": "At every ECG→PPG slip, the step in ECG→ABP and in ABP→PPG was measured. A real slip is a one-frame step in ABP→PPG\nwith ECG→ABP unchanged; a tooth's creep shows as a slope in ABP→PPG that the correction should flatten.\n",
      "results": "- **ART is clean:** raw ART peaks do not lock to the 33.3 ms frame (resultant 0.02, the same as the control).\n  Its median R-to-upstroke delay of 212 ms is physiological, so any arterial path delay is small.\n- **At 351 ECG→PPG slips,** ECG→ABP steps by a median 0.4 ms and ABP→PPG by 32.6 ms. In 3% ECG→ABP also jumps by\n  more than 15 ms: physiological changes that had passed as slips.\n- **Detected slips are real** in 97–100% of cases; slips inferred from ECG→PPG alone are real in only 62%.\n- **Tier A leaves about 2 ms of drift mid-tooth** (an upper bound that includes physiology).\n",
      "discussion": "Used in [[software:physiosync]] as an optional reference, arterial pressure raises confidence and rejects physiological jumps,\nbut adds little coverage.\n",
      "code": [
        "vitaldb-bench/tools/art_check.py"
      ],
      "derived": {
        "observations": [
          "ppg-sawtooth"
        ],
        "examples": [
          "pat-vs-bp",
          "pulse-arrival-time"
        ],
        "code_urls": {
          "vitaldb-bench/tools/art_check.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/art_check.py"
        }
      }
    },
    "ppg-frame-clock": {
      "id": "ppg-frame-clock",
      "name": "The PPG's 30 Hz frame clock",
      "summary": "ECG and PPG fiducials resolve to one 2 ms sample, but PPG fiducials taken on raw samples lock to a hidden 33.3 ms frame grid. Scanning for it finds the oximeter's frame clock to about 1 ppm, a fixed fingerprint of each recording setup.",
      "date": "2026-10-10",
      "status": "current",
      "methods": [
        "signal-borne-clocks"
      ],
      "techniques": [
        "phase-step-detection",
        "null-controls"
      ],
      "clocks": [
        "oximeter-frame-clock",
        "snuadc-sample"
      ],
      "devices": [
        "snuadc",
        "solar8000"
      ],
      "variables": [
        "snuadc-pleth",
        "snuadc-ecg-ii",
        "snuadc-art"
      ],
      "data": "SNUADC ECG II and PLETH at 500 Hz in 266 cases with at least 20 minutes of continuous data (the longest stretch of up to 90 minutes each; 378 hours).",
      "background": "A coarser time grid hidden under the sample grid would limit how finely PPG timing can be read. A published test\n(differences of consecutive inter-beat intervals) found one in other monitor data at 125 Hz.\n",
      "method": "Inter-beat intervals of nine detectors were tested for any grid of 2–16 samples. Fiducials on raw samples were\ntested for locking to a comb at the 33.3 ms frame period (the interval resultant: 0 for no locking, 1 when every\ninterval is a whole number of frames). In absolute sample index, the frame period was scanned in 1 ppm steps for\nthe phase that keeps the systolic peaks on one grid through the whole recording, and the two halves of each\nrecording compared. Arterial pressure was scanned the same way as a control.\n",
      "results": "- **No coarser grid in the intervals:** 50.2% even differences for ECG R peaks and 50.1% for PPG onsets, against\n  50% for no grid.\n- **Raw-sample PPG fiducials lock to the frame:** interval resultant at 33.3 ms of 0.38 for the raw systolic\n  maximum, 0.10 for the raw foot and steepest rise, 0.01 for the sharpest kink, and 0.02 for the steepest upstroke\n  on the filtered signal. The PLETH shows weak lines at 30, 60, 90 and 120 Hz; the 90 Hz line rules out mains hum.\n- **The frame clock is found in 189 of 266 cases;** the two halves of a recording agree within 2 ppm (median). The\n  frame period against the SNUADC sample clock is fixed per setup, for example +84 ppm (IQR 82–88) for one port in\n  step mode, +39 ppm for the same port in frequent-correction mode, +72 and +24 ppm for two others. It keeps its\n  phase through the sawtooth slips.\n- **The best frame anchor** is pyPPG's d point with a light filter (0.5–45 Hz): it finds the clock in 176 cases,\n  against 135 for the raw peak. pyPPG's default filter destroys the lock.\n- **Arterial pressure has no frame** at any period from 2 to 100 samples.\n",
      "discussion": "Locked fiducials carry up to ±17 ms of frame quantisation, so PPG timing should use a fiducial that does not lock\n(the steepest upstroke on the filtered signal). The frame clock is a second clock inside the PPG chain, separate\nfrom the sawtooth of its content ([[study:ppg-sawtooth]]).\n",
      "code": [
        "physiosync/physiosync/frames.py",
        "physiosync/physiosync/quantisation.py",
        "vitaldb-bench/tools/frame_an.py"
      ],
      "figures": [
        {
          "id": "pat-resolution",
          "file": "pat-resolution.png",
          "theme": "distributions of measured parameters",
          "source": "ad hoc (no script in vitaldb-bench yet; to be regenerated by one)",
          "records": [
            2
          ],
          "caption": "PPG fiducials lie on whole 2 ms samples and per-beat PAT noise is about 3.5 ms; detected resets cluster at about 32 ms, one 30 Hz frame. Panel f shows [[case:2]], with a reset every 11.4 min.",
          "alt": "Six panels of histograms and spectra, ending with a sawtooth scatter of PAT against minutes with red reset lines."
        },
        {
          "id": "fiducial-resolution",
          "file": "fiducial-resolution.png",
          "theme": "distributions of measured parameters",
          "source": "vitaldb-bench tools/fid_res.py",
          "caption": "Beat intervals show no grid coarser than 2 ms, but PPG fiducials lock to a 33.3 ms (30 Hz) frame comb.",
          "alt": "Two rows of panels: interval histograms, comb tests, per-case strip plot and ripple bars."
        }
      ],
      "derived": {
        "parameters": [
          "pleth-frame-clock-rate"
        ],
        "observations": [
          "fiducial-quantisation",
          "oximeter-frame-clock-fingerprint",
          "pleth-30hz-frames"
        ],
        "examples": [
          "pulse-arrival-time"
        ],
        "code_urls": {
          "physiosync/physiosync/frames.py": "https://github.com/metrology-atlas/physiosync/blob/main/physiosync/frames.py",
          "physiosync/physiosync/quantisation.py": "https://github.com/metrology-atlas/physiosync/blob/main/physiosync/quantisation.py",
          "vitaldb-bench/tools/frame_an.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/frame_an.py"
        }
      }
    },
    "ppg-offset-reference": {
      "id": "ppg-offset-reference",
      "name": "The PPG hardware delay and the adopted pulse arrival time",
      "summary": "VitalDB's raw ECG→PPG pulse arrival time is about 0.4 s longer than physiology. Splitting it into a hardware delay (one value for the release; [[study:pat-offset-census]]) and a physiological part needs an adopted value from studies that sampled ECG and PPG on one clock: about 270 ms for anaesthetised adults.",
      "date": "2026-10-12",
      "status": "current",
      "methods": [
        "delays-and-windows",
        "source-review"
      ],
      "clocks": [
        "oximeter-frame-clock"
      ],
      "devices": [
        "solar8000",
        "snuadc"
      ],
      "variables": [
        "snuadc-pleth",
        "snuadc-ecg-ii"
      ],
      "data": "232,931 beats in 293 cases (raw PAT); published shared-clock studies of awake adults.",
      "background": "The median R-to-PPG-foot time in VitalDB is 0.64 s, against roughly 0.2–0.3 s in adults measured with shared-clock\nhardware; an earlier VitalDB study found 645 ± 26 ms. The excess is the monitor's buffering and filtering, and it\ncannot be separated from physiology inside VitalDB.\n",
      "method": "Literature graded by how ECG and PPG were clocked: A (one acquisition unit), B (research equipment, shared clock\nlikely), C (clinical monitor, excluded). Adults with an R-peak ECG fiducial and finger PPG foot.\n",
      "results": "- **Tier A studies put R-to-finger-foot PAT at about 200–285 ms** in awake adults; none gives a verifiable\n  shared-clock value under anaesthesia.\n- **Anaesthesia lengthens PAT by about 40 ms** in monitor-based work (240 ms before induction, 279–284 ms after).\n- **For VitalDB** (adults under general anaesthesia, mean age about 58): about 270 ms, plausible range 250–290 ms.\n- **Hardware delay** with that value: about 385 ms over these 293 cases (to the PPG foot). Per-setup values of\n  368–399 ms were reported here first; the full census ([[study:pat-offset-census]], 5,536 recordings) found no\n  difference between setups beyond ±15 ms and the same value, 385 ms (655 ms to the foot minus 270 ms), so one\n  global delay is used. The adopted value and the delay must use the same fiducial: 270 ms is to the foot.\n",
      "discussion": "The adopted value must use the same PPG point the software detects. The global delay is taken at the foot, where\nthe 270 ms applies; a PAT timed to the steepest upstroke comes about 60 ms later, and that difference stays in the\ncorrected value ([[example:pulse-arrival-time]]). Since the census, one delay serves every recording, so a\nchange to the adopted value moves every recording's level by the same amount.\n",
      "limitations": "The hardware delay is not observable in VitalDB; it rests on the adopted physiological value.",
      "references": [
        "allen2000variability",
        "allen2002age",
        "block2020conventional",
        "jose2025exploring",
        "kortekaas2018small",
        "lee2019analysis",
        "liao2023filtering",
        "martin2016weighing",
        "mohammadpoor2024use",
        "obata2017noninvasive",
        "vanvelzen2017increasing",
        "youn2014changes",
        "ruffolo2025high"
      ],
      "figures": [
        {
          "id": "raw-pat-by-setup",
          "file": "raw-pat-by-setup.png",
          "theme": "distributions of measured parameters",
          "source": "ad hoc (no script in vitaldb-bench yet; to be regenerated by one)",
          "caption": "Raw pulse arrival time per recording is similar across monitor ports and correction modes; the steepest upstroke follows the foot by about 60 ms.",
          "alt": "Box-and-strip plot per setup and a histogram of upstroke minus foot."
        },
        {
          "id": "pat-distribution",
          "file": "pat-distribution.png",
          "theme": "distributions of measured parameters",
          "source": "ad hoc (no script in vitaldb-bench yet; to be regenerated by one)",
          "caption": "Measured R-peak to PPG time centres on about 0.64 s, far above the typical 0.2–0.35 s finger pulse arrival time: a hardware delay in the PLETH output.",
          "alt": "Three histograms: per beat, per-case medians, and a pairing-free check, each with the typical range shaded."
        }
      ],
      "derived": {
        "parameters": [
          "adopted-pat"
        ],
        "observations": [
          "pleth-30hz-frames",
          "ppg-hardware-delay"
        ],
        "examples": [
          "pat-vs-bp",
          "pulse-arrival-time"
        ]
      }
    },
    "ppg-sawtooth": {
      "id": "ppg-sawtooth",
      "name": "The PPG sawtooth and its correction",
      "summary": "The ECG→PPG delay creeps at 18–52 ppm and jumps up by one 33.3 ms frame at each slip, every 11–31 minutes. A tooth-by-tooth model removes it; where the correction is confident it is essentially exact on injected test data.",
      "date": "2026-10-10",
      "status": "current",
      "methods": [
        "signal-borne-clocks"
      ],
      "techniques": [
        "beat-matching",
        "phase-step-detection",
        "null-controls"
      ],
      "clocks": [
        "oximeter-frame-clock",
        "snuadc-sample"
      ],
      "devices": [
        "snuadc",
        "solar8000"
      ],
      "variables": [
        "snuadc-pleth",
        "snuadc-ecg-ii"
      ],
      "data": "The same 266 cases (378 hours); a benchmark of a known sawtooth injected into 35 real recordings without one.",
      "background": "SNUADC samples ECG and PPG together, so its own drift cannot move one against the other. Any drift between them\ncomes from inside the monitor.\n",
      "method": "The PPG delay is modelled as D(t) = offset + frame × (slips so far) − creep. The fit detects resets in the\nbeat-matched pulse arrival time and keeps a slip only if it is one frame and falls between two beats\n(physiological changes build over several beats); puts back missed slips only where a gap holds whole teeth and\nthe data show a step; gives each tooth the creep rate frame ÷ length, so a wrong slip distorts only its own tooth;\nand shifts the median to the adopted pulse arrival time. Each tooth gets a confidence tier: A (rate corroborated,\nclean slips, low noise), B, C, N (no sawtooth evident for 45 minutes or 1.5 teeth) or U (possibly affected, not\ncorrected).\n",
      "results": "- **One frame per slip:** the step has a median of 32.3 ms (IQR 30.4–34.8); other sizes are no more common than in\n  a sign-flipped control.\n- **Regular:** slips recur every frame ÷ rate, stable to about 0.2 ppm; a few recordings creep by about 1 ppm over\n  90 minutes.\n- **Fixed per setup, 18–52 ppm,** unrelated to the SNUADC drift (r = −0.22) or the frame clock (r = 0.13). One\n  port hosts two PPG sources: step-mode cases at 51 ppm and frequent-mode cases at 27 ppm.\n- **Invisible in the waveform:** no repeated or held 33 ms segment at a slip (3.5% against 5% by chance).\n\n| Tier | Share of injected time | Error after correction (RMS) | Time over 10 ms | Share of real time |\n|---|---|---|---|---|\n| A | 60% | 0.2 ms | 0.1% | 17.0% |\n| B | 17% | 1.6 ms | 3.7% | 12.2% |\n| C | 19% | 5.3 ms | 14% | 25.0% |\n| U (not corrected) | 3% | 9.0 ms | 34% | 22.8% |\n| N (no sawtooth) | 0.5% (wrongly) | — | — | 23.0% |\n\nUncorrected, the injected sawtooth has an RMS of 9.5 ms.\n",
      "discussion": "Tiers A and B cover 29% of the real time analysed, 52% with the stretches where no sawtooth is running. The\nbenchmark is optimistic: its physiology came from quiet recordings. Labels carry each tooth's tier, so a user can\nchoose how much time to trust.\n\nThe last step of the fit, shifting each recording's median to the adopted pulse arrival time, also removes real\ndifferences between patients. [[study:pat-offset-census]] later showed that one global delay (385 ms,\n[[parameter:pleth-chain-delay]]) fits the whole release, so the atlas removes that instead and keeps those\ndifferences ([[example:pulse-arrival-time]]).\n",
      "code": [
        "physiosync/physiosync/ecg_ppg.py",
        "physiosync/physiosync/labels.py",
        "vitaldb-bench/tools/tier_bench.py",
        "vitaldb-bench/tools/ppg_fix_check.py"
      ],
      "figures": [
        {
          "id": "ppg-correction-check",
          "file": "ppg-correction-check.png",
          "theme": "example traces",
          "source": "ad hoc (no script in vitaldb-bench yet; to be regenerated by one)",
          "records": [
            2156,
            6241,
            1094
          ],
          "caption": "The sawtooth correction removes the PPG frame slips in [[case:2156]], [[case:6241]] and [[case:1094]] (panels a–c). Across 266 cases the steps shrink from 32 ms to 0.2 ms, and the sawtooth rate groups by recording setup rather than by SNUADC drift (panels d–i).",
          "alt": "Three long PAT traces of measured and corrected values beside six aggregate histograms and scatter plots."
        },
        {
          "id": "pat-timeseries",
          "file": "pat-timeseries.png",
          "theme": "example traces",
          "source": "ad hoc (no script in vitaldb-bench yet; to be regenerated by one)",
          "records": [
            1912,
            2,
            2153,
            2611,
            476,
            3964,
            4194,
            5581,
            489,
            1599,
            1759,
            1858
          ],
          "caption": "Pulse-foot PAT through 12 cases, SNUADC clock corrections dashed. Most ([[case:2]], [[case:4194]], [[case:5581]]) show a regular sawtooth that does not follow the corrections; [[case:489]] shows none; [[case:3964]] and [[case:1759]] are noisy edge cases.",
          "alt": "Twelve scatter plots of 30 s median PAT against hours, with orange dashed vertical lines."
        }
      ],
      "derived": {
        "parameters": [
          "pleth-sawtooth-rate",
          "pleth-slip-step"
        ],
        "observations": [
          "pleth-30hz-frames",
          "ppg-hardware-delay",
          "ppg-sawtooth"
        ],
        "examples": [
          "pat-vs-bp",
          "pulse-arrival-time"
        ],
        "code_urls": {
          "physiosync/physiosync/ecg_ppg.py": "https://github.com/metrology-atlas/physiosync/blob/main/physiosync/ecg_ppg.py",
          "physiosync/physiosync/labels.py": "https://github.com/metrology-atlas/physiosync/blob/main/physiosync/labels.py",
          "vitaldb-bench/tools/tier_bench.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/tier_bench.py",
          "vitaldb-bench/tools/ppg_fix_check.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/ppg_fix_check.py"
        }
      }
    },
    "ppg-sawtooth-pauses": {
      "id": "ppg-sawtooth-pauses",
      "name": "Where the PPG sawtooth stops",
      "summary": "In some stretches no slip comes although several were due. The PPG delay there is flat, not hidden: the monitor keeps framing, and only the content's drift against the frames has stopped.",
      "date": "2026-10-11",
      "status": "current",
      "methods": [
        "signal-borne-clocks"
      ],
      "techniques": [
        "phase-step-detection",
        "null-controls"
      ],
      "clocks": [
        "oximeter-frame-clock"
      ],
      "devices": [
        "solar8000",
        "snuadc"
      ],
      "variables": [
        "snuadc-pleth",
        "snuadc-art"
      ],
      "data": "98 stretches (67 hours) where slips were due but none was seen; 32 whole recordings (47 hours) without a slip.",
      "method": "In each stretch the case's own sawtooth (one frame, at the recording's rate, best of 50 phases) was fitted against\na mirrored sawtooth as the control. Slip prediction looked one tooth beyond the first and last accepted slips and\naccepted a slip there only if a step showed.\n",
      "results": "- **The delay is flat:** median slope −0.15 times the sawtooth rate; only 13 stretches creep without slipping.\n- **The frame clock keeps running** through them: found in 82 of 98 stretches, at the rate of the corrected part of\n  the same case to within 1 ppm.\n- **Whole recordings without slips are robust,** but at least one shows a slow drift of about 7 ppm and a 20 ms\n  step instead: possibly a much slower sawtooth.\n- **Slip prediction** moved tiers A + B from 26% to 29% of time, with unchanged error.\n",
      "discussion": "Stretches that pass the test are labelled N (offset only, no sawtooth). Recordings whose own tooth rate differs\nfrom their setup's by more than 20% use their own rate, and none of their teeth is labelled N.\n",
      "limitations": "What replaces the sawtooth at the start and end of some recordings is not simply flat (fast ABP→PPG ramps of several ms per minute). Open.",
      "open_questions": [
        {
          "title": "What replaces the sawtooth",
          "kind": "phenomenon",
          "summary": "At the start and end of some recordings the PPG lag is not flat but ramps by several ms per minute against arterial pressure. Its cause is not known."
        }
      ],
      "figures": [
        {
          "id": "sawtooth-pauses",
          "file": "sawtooth-pauses.png",
          "theme": "example traces",
          "source": "ad hoc (no script in vitaldb-bench yet; to be regenerated by one)",
          "caption": "Three anonymous recordings where the PPG sawtooth pauses, against the sawtooth expected had it continued.",
          "alt": "Three stacked traces of arterial-pressure-to-PPG lag with shaded pause spans and dashed expected sawtooth lines."
        }
      ],
      "derived": {
        "observations": [
          "ppg-sawtooth-pauses"
        ],
        "examples": [
          "pulse-arrival-time"
        ]
      }
    },
    "primus-pause-steps": {
      "id": "primus-pause-steps",
      "name": "The Primus stamp step at each airway-pressure pause",
      "summary": "Measured directly at 2,491 pauses in 1,021 recordings: where CO2 carries on through the AWP pause (74%), the Primus stamps barely step (median −1 ms, p10–p90 −164 to +187 ms, close to the no-pause controls); where CO2 stops too (26%), they step by up to seconds. No step undoes the drift built up since the last pause.",
      "date": "2026-10-19",
      "status": "current",
      "methods": [
        "shared-events"
      ],
      "techniques": [
        "windowed-cross-correlation",
        "null-controls"
      ],
      "clocks": [
        "primus-clock",
        "snuadc-sample",
        "recorder-pc"
      ],
      "devices": [
        "primus",
        "snuadc"
      ],
      "variables": [
        "primus-awp",
        "primus-co2"
      ],
      "data": "All 6,388 recordings were read; 1,021 had AWP, CVP, at least one AWP pause with enough breathing either side, and\nat least 200 mechanical breaths. 2,491 of their 3,665 pauses were measurable; up to two control moments per\nrecording at least 25 minutes from any pause.\n",
      "background": "At each AWP pause (about 21 s every 7,230 s; [[observation:primus-awp-pauses]]) the Primus stamps step against the\nSNUADC. The 10-minute windows of [[study:ventilator-breath-matching]] see these steps only through fitted lines,\nand windows that straddle a pause mix two lags.\n",
      "method": "The airway-pressure-to-CVP lag in 2-minute windows (stepped by 1 minute) for 20 minutes before and after each gap,\nwith the SNUADC on its stamp model; a line on each side, and the step as their difference at the middle of the gap.\nRecorded with each step: gap length, drift built up since the previous pause, the Primus's small whole-sample\ncorrections nearby, whether CO2 also stops, and whether a SNUADC reset falls within 20 minutes. Controls: the same\nmeasurement where no pause is near.\n",
      "results": "| Pauses | Count | Step, median (p10–p90) | Within 150 ms |\n|---|---|---|---|\n| CO2 carries on | 1,846 | −1 ms (−164 to +187) | 76% |\n| CO2 stops too | 645 | −269 ms (−1,965 to +1,609) | 8% |\n| Controls, no pause | | −10 ms (−144 to +162) | |\n\n- **Unrelated to the drift built up** since the previous pause (r = −0.03; 0.3% of it taken back), to the gap's\n  length (r = −0.05) and to the small whole-sample corrections (r = 0.00).\n- **A nearby SNUADC reset** does not enlarge the step (median |step| 87 ms with, 119 ms without).\n- **Precision:** a median standard error of 37 ms per step.\n\n[[figure:primus-pause-steps]]\n",
      "discussion": "The regular AWP pause is not a clock correction in itself: when the CO2 stream carries on, the stamps keep their\ntiming. The large steps come when both waveforms stop, which looks like the recorder losing and re-opening the\nPrimus connection and re-anchoring its stamps to the PC. Even so, synchronisation is best with a fresh offset and\nslope at every AWP pause, not only at breaks: predicting each 10-minute window from the others gives a median error\nof 55 ms that way, against 82 ms with new lines only at breaks and 64 ms with lines joined at the regular pauses\n(1,067 recordings, [[code:vitaldb-bench/tools/primus_pause_aware.py]]). The regular pauses are small steps on\naverage, but not small enough to ignore.\n",
      "limitations": "The step includes any change in the physiological airway-to-CVP delay across the pause. Pauses within 20 minutes\nof a recording's edge, or with too little breathing either side, are not measured.\n",
      "open_questions": [
        {
          "title": "What the stamps re-anchor to when both waveforms stop",
          "kind": "hypothesis",
          "summary": "Steps of up to seconds come where AWP and CO2 stop together. A re-opened connection re-anchoring the stamps to the PC's arrival time is the working explanation; the recorder's message log around those gaps would test it."
        }
      ],
      "code": [
        "vitaldb-bench/tools/primus_pause_steps.py",
        "vitaldb-bench/tools/primus_co2_gaps.py",
        "vitaldb-bench/tools/primus_pause_aware.py",
        "vitaldb-bench/tools/clock_route_figs.py"
      ],
      "figures": [
        {
          "id": "primus-pause-steps",
          "file": "primus-pause-steps.png",
          "theme": "population results",
          "source": "vitaldb-bench tools/clock_route_figs.py",
          "caption": "The Primus stamp step at each AWP pause against the SNUADC, measured from 20 minutes of breath-matched lag either side. Blue: pauses where CO2 carries on (1,846), almost all within the spread of the no-pause controls (grey band, p10–p90). Orange: pauses where CO2 stops too (645), spread over seconds; values beyond ±2 s are gathered at the edges.",
          "alt": "Two histograms of step size from −2 to +2 s. The blue one is a tall narrow peak at zero inside a grey band of about ±150 ms; the orange one is flat across the whole range with spikes at both edges."
        }
      ],
      "derived": {
        "observations": [
          "primus-awp-pauses"
        ],
        "code_urls": {
          "vitaldb-bench/tools/primus_pause_steps.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/primus_pause_steps.py",
          "vitaldb-bench/tools/primus_co2_gaps.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/primus_co2_gaps.py",
          "vitaldb-bench/tools/primus_pause_aware.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/primus_pause_aware.py",
          "vitaldb-bench/tools/clock_route_figs.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/clock_route_figs.py"
        }
      }
    },
    "rare-device-sync": {
      "id": "rare-device-sync",
      "name": "Rare devices against SNUADC",
      "summary": "CardioQ, EV1000, Vigileo and Vigilance each get a link to the SNUADC timeline: beat matching for the CardioQ, mean arterial pressure alignment for the EV1000 and Vigileo, and a long-window fit for Vigilance's averaged heart rate.",
      "date": "2026-10-13",
      "status": "current",
      "methods": [
        "shared-events",
        "delays-and-windows"
      ],
      "techniques": [
        "beat-matching"
      ],
      "clocks": [
        "cardioq-clock",
        "snuadc-sample",
        "recorder-pc"
      ],
      "devices": [
        "cardioq",
        "ev1000",
        "vigileo",
        "vigilance",
        "snuadc"
      ],
      "variables": [
        "cardioq-abp",
        "ev1000-art-mbp",
        "vigileo-sv",
        "vigilance-co",
        "snuadc-art",
        "snuadc-ecg-ii"
      ],
      "data": "997 cases with at least one of the rarer haemodynamic monitors, each with SNUADC arterial pressure or ECG as the\nreference:\n\n- **CardioQ:** 27 cases tried, 24 fitted.\n- **EV1000 mean arterial pressure:** 588 tried, 579 fitted (stroke volume variation as a second signal: 67 of 589).\n- **Vigileo stroke volume variation:** 343 tried, 25 aligned.\n- **Vigilance averaged heart rate:** 63 cases; 52 fitted (11 lacked HR_AVG or ECG).\n",
      "background": "A few hundred cases carry haemodynamic monitors besides the main ones. Their stamps re-anchor irregularly or\ndescribe long averages, so each needs its own route to the reference.\n",
      "method": "- **CardioQ:** beats in its arterial pressure matched to SNUADC arterial pressure within each re-anchored stretch.\n- **EV1000, Vigileo:** their mean pressure or stroke volume variation aligned with the same quantity computed from\n  SNUADC arterial pressure, one offset per 30-minute window.\n- **Vigilance:** its averaged heart rate fitted with long windows (mean and median over 32–4,096 beats or\n  30–3,600 s, exponential averages) against ECG R–R intervals, with the delay held at zero or above.\n",
      "results": "- **CardioQ:** matched-beat MAD 0.24 s, R–R correlation 0.96 (24 cases); its stamps lag SNUADC by 0.4–4.4 s\n  (p5–p95) and re-anchor about 40 times per case.\n- **EV1000:** values sit a median 1.6 s (p25 0.9, p95 3.2 s; 579 cases) after the end of an assumed 20 s window;\n  after alignment the residual SD is 0.25–2.4 s.\n- **Vigileo:** aligned in only 25 of 343 cases (|offset| 4.7 s median, p95 26 s).\n- **Vigilance:** a running median over 600–2,185 s (median 1,050 s, about 17 minutes), available 0–175 s after\n  its window ends (median 51 s); 16 of 52 cases sit at zero delay.\n",
      "discussion": "The three-way arterial pressure loop confirms the CardioQ and EV1000 links against each other\n([[study:loop-closure]]). Vigilance values describe the last 10–36 minutes; alignment finer than minutes is not\nmeaningful.\n",
      "limitations": "Small samples; Vigileo alignment is weak and Vigilance's fit is a pilot.",
      "open_questions": [
        {
          "title": "Vigileo rarely aligns",
          "kind": "phenomenon",
          "summary": "Vigileo aligns in only 25 of 343 cases; whether its values are too smooth or its clock too unstable is not known."
        },
        {
          "title": "Vigilance: 13 cases or 52",
          "kind": "conflict",
          "summary": "Vigilance fits differ between the two analyses (13 cases in the rare-device scan, 52 in the window study); the difference has not been reconciled."
        }
      ],
      "code": [
        "vitaldb-bench/tools/rare_device_sync.py",
        "vitaldb-bench/tools/vigilance_window.py"
      ],
      "derived": {
        "parameters": [
          "cardioq-snuadc-lag"
        ],
        "code_urls": {
          "vitaldb-bench/tools/rare_device_sync.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/rare_device_sync.py",
          "vitaldb-bench/tools/vigilance_window.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/vigilance_window.py"
        }
      }
    },
    "record-messages-and-steps": {
      "id": "record-messages-and-steps",
      "name": "Message sizes and the steps between records",
      "summary": "Every record in every waveform: SNUADC writes messages of 100 samples (0.2 s), Primus 30 (0.48 s), BIS 16 (0.125 s), CardioQ no fixed size. Backward steps outnumber gaps by about a hundred to one and come from a few recordings with duplicated or interleaved records. Primus gaps are whole lost messages; most SNUADC and BIS gaps are not.",
      "date": "2026-10-19",
      "status": "draft",
      "methods": [
        "data-continuity"
      ],
      "techniques": [
        "remainder-test"
      ],
      "clocks": [
        "recorder-pc",
        "snuadc-sample",
        "bis-clock",
        "primus-clock"
      ],
      "devices": [
        "vital-recorder",
        "snuadc",
        "primus",
        "bis",
        "cardioq"
      ],
      "variables": [
        "snuadc-ecg-ii",
        "snuadc-pleth",
        "snuadc-art",
        "primus-co2",
        "primus-awp",
        "bis-eeg"
      ],
      "code": [
        "vitaldb-bench/tools/record_scan.py",
        "vitaldb-bench/tools/record_sizes_agg.py",
        "vitaldb-bench/tools/record_steps_collect.py",
        "vitaldb-bench/tools/record_steps_figs.py"
      ],
      "data": "All 6,388 recordings; every waveform record (about 2.5 billion records). Run-length encoded record sizes and\nevery non-zero step (5.2 million) were cached per recording.\n",
      "background": "Each record carries one stamp and a block of samples. The next record should start where the last one ended;\nthe step is the difference: step = start(k+1) − (start(k) + size(k) / rate). Positive steps are gaps, negative\nsteps overlaps. A device that sends fixed-size messages loses data in whole messages, so a gap that is not a\nwhole number of messages means a clock correction, or a loss with one.\n",
      "method": "- Per track, the distribution of record sizes; the message is the modal size.\n- Every step of 1 µs or more, forward and backward, histogrammed on a log scale.\n- Remainder test: gaps longer than one message, folded modulo the message duration.\n",
      "results": "- **Message sizes.** SNUADC: 100 samples at 500 Hz, 0.2 s (99.9% of records; a few 50-sample halves and about\n  300 rarer sizes). Primus: 30 samples at 62.5 Hz, 0.48 s. BIS: 16 samples at 128 Hz, 0.125 s (all but a\n  handful). CardioQ: no fixed size (about 2,500 distinct sizes, median about 117 samples, 0.65 s).\n- **Backward steps dominate.** SNUADC ECG II: 761,225 backward against 6,205 forward; BIS EEG: 1.05 million\n  against 10,789; Primus AWP 101,805 against 20,397. They form a comb of discrete values from about 10 ms up\n  to one message, and 98% of SNUADC's are in 27 recordings. Two mechanisms explain them:\n  [[observation:duplicated-records]] (a whole message written twice with the same stamp: a step of exactly\n  −1 message) and [[observation:interleaved-records]] (two streams alternating in one track: steps in pairs\n  that sum to one message).\n- **Primus gaps are whole messages.** Folded modulo 0.48 s, 11,000 of 14,989 AWP gaps sit at zero: lost\n  messages, with the stamps intact. CO2 has the same peak at zero plus a second near 0.43 of a message.\n- **SNUADC and BIS gaps mostly are not.** Their remainders are spread almost evenly, with small peaks at 0 and\n  1 (whole messages) and, in BIS, a comb at multiples of 1/8 of a message (2 samples). Most of these gaps are\n  clock corrections or a loss combined with one ([[observation:small-snuadc-gaps]] shows that SNUADC's\n  sub-second gaps do lose data).\n- CardioQ forward and backward steps are symmetric around one message: its stamps jitter by about a message.\n\n[[figure:record-steps-magnitude]]\n\n[[figure:record-steps-remainder]]\n",
      "discussion": "A gap rule on the stamps alone sees only the forward steps, a small share of the irregularities. Knowing the\nmessage size per device turns the stamps into a test: a whole number of messages is a loss, a remainder is a\nclock correction. [[study:step-classification]] combines this with the samples on either side of each step.\n",
      "limitations": "- Steps under 1 µs are not kept; float rounding of stamps makes some zero steps non-zero.\n- The remainder test cannot separate a correction from a loss plus a correction.\n",
      "open_questions": [
        {
          "title": "Lost data or a moved clock?",
          "kind": "next step",
          "summary": "For SNUADC and BIS gaps that are not whole messages, test whether the waveform continues across the gap (no loss) or needs missing samples to fit ([[method:signal-continuity]]). Partly answered by [[study:step-classification]]: steps under one message run on (clock corrections), while about 70% of SNUADC fractional gaps and roughly a third of BIS fractional gaps lose data. What is left is fitting how much."
        }
      ],
      "figures": [
        {
          "id": "record-steps-magnitude",
          "file": "record-steps-magnitude.png",
          "theme": "data continuity",
          "source": "vitaldb-bench tools/record_steps_figs.py",
          "caption": "Size of every non-zero step between records, forward (gaps, blue) and backward (overlaps, orange), log scale, 20 bins per decade; dotted lines at one sample and one message. Backward steps form a comb below one message and outnumber gaps by about a hundred to one in SNUADC and BIS; Primus has a spike at exactly one message.",
          "alt": "Eight log-scale histograms; orange combs of backward steps between 10 ms and one message tower over the blue forward steps."
        },
        {
          "id": "record-steps-remainder",
          "file": "record-steps-remainder.png",
          "theme": "data continuity",
          "source": "vitaldb-bench tools/record_steps_figs.py",
          "caption": "Gaps longer than one message, folded modulo the message duration. Primus AWP gaps are whole messages (all at zero); CO2 has a second peak near 0.43; SNUADC and BIS remainders are spread evenly, with small peaks at 0 and 1 and, in BIS, a comb at eighths of a message.",
          "alt": "Eight histograms of gap remainders from 0 to 1; AWP is a single bar at 0; SNUADC and BIS are roughly flat."
        }
      ],
      "derived": {
        "observations": [
          "duplicated-records",
          "interleaved-records"
        ],
        "code_urls": {
          "vitaldb-bench/tools/record_scan.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/record_scan.py",
          "vitaldb-bench/tools/record_sizes_agg.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/record_sizes_agg.py",
          "vitaldb-bench/tools/record_steps_collect.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/record_steps_collect.py",
          "vitaldb-bench/tools/record_steps_figs.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/record_steps_figs.py"
        }
      }
    },
    "recorder-pc-steps": {
      "id": "recorder-pc-steps",
      "name": "Recorder PC clock steps and recording pauses",
      "summary": "A step of the recorder PC clock would move every device's stamps at once. Searching all 6,388 files for such simultaneous jumps found none of 0.5 s or more; what moves everything together is recording pauses.",
      "date": "2026-10-17",
      "status": "current",
      "methods": [
        "stamp-reading"
      ],
      "techniques": [
        "coincident-jumps"
      ],
      "clocks": [
        "recorder-pc",
        "utc",
        "snuadc-sample",
        "bis-clock"
      ],
      "devices": [
        "vital-recorder",
        "solar8000",
        "primus",
        "snuadc",
        "bis"
      ],
      "data": "All 6,388 files; 19,006 recording hours with both Solar8000 and Primus numerics.\n",
      "background": "The link from the recorder PC to UTC is the only one in the timing graph that cannot be measured from the\nsignals, because the release shifts every date. But a step of the PC clock (a time synchronisation, a manual\nchange) would leave a trace: every record stamped after it moves by the same amount, whatever its device.\nNumeric tracks such as the Solar8000 and Primus numerics have no device clock of their own, so they show such a\nstep most directly.\n",
      "method": "For every file, the intervals between consecutive Solar8000 HR and Primus ETCO2 records were scanned for jumps\nthat appear in both at the same time, and the SNUADC and BIS waveform stamps were checked for a jump at the same\nmoment. A PC step shows in all of them; a device's own correction shows in one.\n",
      "results": "- **No PC step of 0.5 s or more** in any file.\n- **No backward steps.** No numeric track stamped by the PC (Solar8000, Primus, Orchestra, EV1000, Vigileo,\n  Vigilance, CardioQ) ever steps backwards. The only back-steps are in BIS numerics, in 9 files, which suggests\n  the BIS monitor stamps its own numerics.\n- **Recording pauses explain the familiar gaps.** In 1,846 files (29%) there are 2,025 moments when every device\n  jumps forward together, by a median 19.6 s (p10–p90 15.7–29.8 s). Data are missing there, and the stamps\n  show it honestly.\n- **Small simultaneous delays are not PC steps.** 25 delays of 0.5–0.9 s in 17 files hit both numerics with no\n  waveform jump, and are followed by an ordinary next SNUADC reset (−0.481 s) and ordinary BIS corrections. The\n  PC clock did not move; these are recorder stalls or shifts in polling.\n",
      "discussion": "The PC–UTC link stays an assumption, but now a bounded one: whatever the time synchronisation does, it never\nsteps the clock by half a second or more. Steps smaller than that, and the gradual slewing Windows normally uses,\nremain invisible.\n",
      "limitations": "Detection is limited to steps of 0.5 s and above. A slow slew of the PC clock cannot be told apart from device\ndrift with these data.\n",
      "open_questions": [
        {
          "title": "PC clock steps, not slews",
          "kind": "assumption",
          "summary": "The analysis assumes the recorder PC's clock corrects in steps. Slow slewing would look like device drift and cannot be told apart from it with these data."
        }
      ],
      "code": [
        "vitaldb-bench/tools/pc_clock_steps.py"
      ],
      "derived": {
        "parameters": [
          "pc-step-bound",
          "recording-pause"
        ],
        "observations": [
          "recording-pauses",
          "release-time-shift"
        ],
        "code_urls": {
          "vitaldb-bench/tools/pc_clock_steps.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/pc_clock_steps.py"
        }
      }
    },
    "reset-cluster-order": {
      "id": "reset-cluster-order",
      "name": "Records out of order at SNUADC resets, and where the lost time goes",
      "summary": "At every SNUADC reset the five records after it are written out of order: two continue the old timing and three start the new. Put back in order, the waveform runs on with no samples missing; the 18–20 ms lost at each reset goes missing inside the last old-timing record, and an extra zero sample follows.",
      "date": "2026-10-17",
      "status": "current",
      "methods": [
        "stamp-reading",
        "signal-continuity"
      ],
      "techniques": [
        "junction-scores",
        "null-controls"
      ],
      "clocks": [
        "snuadc-sample",
        "recorder-pc"
      ],
      "devices": [
        "snuadc",
        "vital-recorder"
      ],
      "variables": [
        "snuadc-ecg-ii",
        "snuadc-art",
        "snuadc-pleth"
      ],
      "data": "315 resets with the standard stamp pattern, then 392 resets in 174 of 182 step-mode cases with clock fits, in\nevery SNUADC channel. A census of the record stamps of all 6,388 files (17 October;\n[[code:vitaldb-bench/tools/snuadc_stamp_census.py]]) then classified every place a record is stamped before the\nprevious one ends, in the 6,321 files whose records do not overlap throughout.\n",
      "background": "The correction scan ([[study:clock-correction-scan]]) found that the records inside a reset join badly, and the\npaced ECG ([[study:paced-ecg]]) that 17.7 ms of signal is lost at each reset with no samples missing at any\njunction. The SNUADC firmware schedules each sample 2 ms after the last, or at once if it is late, so a stall\nlonger than 2 ms is never sampled.\n",
      "method": "For each reset, the records were classified by stamp: on the old grid (the last record before the reset plus\n0.2 s, 0.4 s) or on the new grid (the first early-stamped record plus 0.2 s, 0.4 s). Junction scores compared\nfile order with old-grid-then-new-grid order. With the records reordered and the zero sample removed, every\nsample boundary within ±0.3 s of the cluster was tested for a time gap of m samples: the 20 samples either side\nwere fitted with one quadratic with the second side shifted by m, for m = 0 to 15, on the plethysmogram and\narterial pressure. Random windows far from resets were the control.\n",
      "results": "- **The order:** two old-grid records, then three new-grid records, each in file order (usually file positions\n  1 and 3, then 0, 2 and 4). Every junction is then as smooth as an ordinary record boundary, no samples are\n  missing at any of them, and the stamps make one clean step back of −0.480 s.\n- **The same clusters in every SNUADC channel**, and every sample survives. About 1 reset in 16 interleaves\n  differently and gives one record its neighbour's stamp; the same rule orders it (24 records restamped). 9 resets\n  were already in order.\n- **One extra zero sample:** 335 of 392 resets hold one sample reading 0 in every channel at once. Lines fitted\n  either side meet as if it were not there (−0.97 samples, median), so it carries no time: a filler.\n- **The lost time is in the last old-grid record.** 81 of 97 confident plethysmogram detections (m = 6–13) fall\n  in that record, against about 2 expected by chance; with arterial pressure, 99 of 392 resets show it there.\n  The gap is about 11 samples (IQR 10–12), at no fixed position, and always before the zero sample.\n- **In every file (census):** 1,785 files write their reset clusters out of order (2,569 resets, 178 of them\n  with a repeated stamp), and 820 files write them already in order (988 resets). **No file does both**, which\n  points to two recorder or firmware versions. 12 backward steps of reset size match neither (left as written;\n  most are small interleaved steps of 8–12 ms), and 496 are small steps of the frequent-correction mode.\n- **34 files must be excluded from the rule.** Their records overlap the next record far more often than resets\n  do (in 20 of them, 4–95% of all record junctions step back). There the rule matches 115,487 times by\n  coincidence and would scramble the signal. None of these files is in the current builds or clock fits; the\n  import needs a guard before a full build (see [[observation:snuadc-overlapping-records]]).\n\n[[figure:reset-classes]]\n",
      "discussion": "Sampling pauses for about 20 ms just before the recorder starts the new grid, and the time is never sampled; a\nfiller sample follows. With the filler counted, the true pause is about 19.7 ms, consistent with the 17.7 ms lost\non the sample count. The [[code:timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/connector.py|TimeNet import]] now puts the records in sampling order\n([[code:timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/resets.py|resets.py]]), and the clock fits use the same order. The zero sample is\nstill stored, and the loss is added at the end of the record rather than at its true point inside it.\n",
      "limitations": "The gap is located only where the signal is steep (about one reset in four). The public firmware may not be the\nversion deployed. The census classifies by stamps alone; the 988 in-order resets were not checked in the signal.\n",
      "open_questions": [
        {
          "title": "Two firmware versions behind the reset order",
          "kind": "hypothesis",
          "summary": "1,785 files write their reset clusters out of order and 820 in order, and no file does both. Two recorder or firmware versions would explain the clean split, but nothing in the release shows which file ran which; file headers and recording dates (hidden in the release) would tell."
        },
        {
          "title": "34 files whose records overlap throughout",
          "kind": "phenomenon",
          "summary": "Mostly explained since: records written twice and two interleaved streams ([[observation:duplicated-records]], [[observation:interleaved-records]]); whether these account for all 34 is not checked ([[observation:snuadc-overlapping-records]])."
        },
        {
          "title": "Twelve unmatched resets",
          "kind": "phenomenon",
          "summary": "Twelve unmatched resets have small interleaved steps of 8–12 ms. Whether they are resets of a different size or something else is not known."
        },
        {
          "title": "The filler sample",
          "kind": "phenomenon",
          "summary": "Why most resets hold one extra zero sample, a little after the gap, is not known."
        },
        {
          "title": "A guard on the reorder rule for overlapping files",
          "kind": "next step",
          "summary": "The reorder rule matches 115,487 coincidental clusters in the 34 overlapping files. None of them are in the current builds or fits, but before any full import the rule must detect these files and leave them alone, with a test on one of them."
        },
        {
          "title": "Census figures without the anomalous files",
          "kind": "next step",
          "summary": "The census summary now leaves out the 34 anomalous files and counts them separately. The figures made before that change still include them, so they need re-exporting from the new summary, and the summary change needs committing in vitaldb-bench."
        }
      ],
      "code": [
        "timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/resets.py",
        "timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/tests/test_resets.py",
        "vitaldb-bench/tools/clock_model_fit.py",
        "vitaldb-bench/tools/adhoc/reset_gapscan.py",
        "vitaldb-bench/tools/adhoc/reset_glitch.py"
      ],
      "figures": [
        {
          "id": "reset-classes",
          "theme": "drift and resets",
          "source": "vitaldb-bench tools/snuadc_stamp_census.py",
          "file": "reset-census-classes.png",
          "caption": "Every SNUADC reset in the release, by what the reset-order rule does with it (files with records overlapping throughout left out).",
          "alt": "Bar chart of reset counts by class; most resets are reordered by the standard rule, about a thousand were already in order, and twelve match neither."
        },
        {
          "id": "reset-unmatched",
          "theme": "drift and resets",
          "source": "vitaldb-bench tools/snuadc_stamp_census.py",
          "file": "reset-census-unmatched.png",
          "caption": "The stamp patterns of the twelve reset-sized backward steps the rule leaves as written (gaps between the next six records, in units of 10 ms).",
          "alt": "Horizontal bar chart of the most common stamp patterns among unmatched backward steps."
        }
      ],
      "derived": {
        "observations": [
          "loaders-lose-samples",
          "reset-records-out-of-order",
          "reset-signal-lost",
          "reset-zero-sample",
          "snuadc-one-clock",
          "snuadc-overlapping-records"
        ],
        "code_urls": {
          "timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/resets.py": "https://github.com/OpenTSLM/TimeNet/blob/main/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/resets.py",
          "timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/tests/test_resets.py": "https://github.com/OpenTSLM/TimeNet/blob/main/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/tests/test_resets.py",
          "vitaldb-bench/tools/clock_model_fit.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/clock_model_fit.py",
          "vitaldb-bench/tools/adhoc/reset_gapscan.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/adhoc/reset_gapscan.py",
          "vitaldb-bench/tools/adhoc/reset_glitch.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/adhoc/reset_glitch.py"
        }
      }
    },
    "setup-identification": {
      "id": "setup-identification",
      "name": "Identifying the recording setup of every case",
      "summary": "Recognising which equipment made each recording, from its serial ports and, where those fall short, from clock fingerprints, so that per-setup values can be used for every case.",
      "date": "2026-10-18",
      "status": "open",
      "methods": [
        "stamp-reading",
        "signal-borne-clocks"
      ],
      "techniques": [
        "unit-mixture"
      ],
      "devices": [
        "vital-recorder",
        "snuadc",
        "bis",
        "solar8000",
        "primus"
      ],
      "code": [
        "vitaldb-bench/tools/setup_assign.py",
        "vitaldb-bench/tools/frame_an.py"
      ],
      "data": "The device records of all 6,388 .vital files (each device's serial port), and the per-case drifts of\n[[study:drift-units]].\n",
      "background": "Many timing parameters hold per setup ([Recording setups](../setups.md)). The PC key, the ports of the four core\ndevices, is complete for 90% of recordings and puts the SNUADC's drift within 3 ppm of its setup's median in 97% of\ncases ([[observation:device-drift-by-setup]]).\n",
      "results": "- Ports are recorded for almost every device (SNUADC 6,355 of 6,355, Solar8000 6,333 of 6,388, BIS 5,853 of 5,853).\n- The PC key is complete for 5,755 recordings (90%) and takes 65 values; 37 of them have at least 20 recordings,\n  together 5,631.\n- Most of the remaining 10% have no BIS.\n- **Assignment by Bayes' rule** ([[code:vitaldb-bench/tools/setup_assign.py]]): the candidates are the\n  complete keys that agree on every core port a recording has; each is scored by how many recordings it has, by\n  the ports of the recording's other devices (Orchestra, EV1000, Vigileo and so on) and, where measured, by the\n  SNUADC's drift against the key's median.\n- **Validation** on the complete recordings, hiding one core port at a time and scoring each recording without\n  itself: a hidden BIS port is recovered in 94% of recordings, and in 99% of the 5,117 assigned with a posterior\n  of at least 0.9; a hidden Solar8000 or Primus port in more than 99.7%; a hidden SNUADC port in 83% (99% of the\n  3,673 at 0.9 or more). Below 0.9 the posteriors are only roughly calibrated.\n- **Assigned:** 517 of the 633 incomplete recordings get a key with a posterior of at least 0.9, among them\n  468 of the 519 without a BIS; 33 more between 0.5 and 0.9. 51 match no complete key, most of them missing the\n  Solar8000's port, and 32 stay below 0.5.\n- **The oximeter frame clock** (18 October; [[code:vitaldb-bench/tools/frame_an.py]] on up to 30 complete\n  recordings per key and the 116 unassigned): 1,169 of 1,350 recordings had a usable ECG and PLETH stretch, and\n  the raw PPG peak locked to a frame clock, with both halves agreeing within 3 ppm, in 656. Within a key the\n  frame clock is a strong fingerprint: 88% of recordings lie within 3 ppm of their key's median, against 19% of\n  the release's median, and key medians range from +7 to +92 ppm. Two keys hold two clearly different rates\n  (interquartile ranges of 57–58 ppm), probably two oximeter modules on one PC.\n- **With the frame clock added** to the assignment, 521 of the 633 incomplete recordings reach a posterior of\n  at least 0.9 (517 before), and those below 0.5 fall from 31 to 18. The gain is small because only 40 of the\n  incomplete recordings have a frame clock: most of the rest have no usable PLETH stretch or no lock. Validation\n  is unchanged within a percentage point.\n",
      "open_questions": [
        {
          "title": "Does the setup still fix the drift on the whole release?",
          "kind": "conflict",
          "summary": "The register's drifts come from the correction scan's cases (472 SNUADC step-mode and 1,627 BIS recordings, one duration-weighted rate per recording, including the −0.95 ppm stamp arithmetic). There the PC key puts 97% of SNUADC and 83% of BIS drifts within 3 ppm of their setup's median. Rerun on the whole release's correction intervals ([[study:device-correction-schedules]]: 642 SNUADC and 1,939 BIS recordings), the same test gives 71% and 60% (against 25% and 17% for the release median). The setup still narrows the drift a great deal, but less than the register says; the difference may be the drift's variation within a recording (a median 3.5 ppm for the BIS), which a single interval does not average out. Which rate per recording the register should use is not decided."
        },
        {
          "title": "Assigning the last 112 recordings to a setup",
          "kind": "next step",
          "summary": "Done for most: 521 of the 633 recordings without a full key are assigned with a posterior of at least 0.9 (99% right in validation, with the frame clock added). The remaining 112 have no matching key (51) or no fingerprint that separates their candidates; most lack a usable PLETH stretch for the frame clock. They take the release's values until another fingerprint is found."
        },
        {
          "title": "Splitting a PC key into the units it carried",
          "kind": "next step",
          "summary": "A key is a PC's cabling, not its devices: the busiest key holds 755 recordings, its SNUADC's and BIS's drifts show more than one unit, and two keys hold two distinct oximeter frame clocks. Drift units and frame clocks could split a key into periods with one set of devices, which is what the per-setup delays and rates really belong to."
        },
        {
          "title": "Do the ways of recognising a setup agree?",
          "kind": "next step",
          "summary": "Tabulate, per PC key, the drift units, the oximeter's frame clock, the PPG sawtooth rate and delay, the correction mode and the heart-rate delay group, to see whether they name the same setups and where a key hides two."
        },
        {
          "title": "Does the value side depend on the setup?",
          "kind": "next step",
          "summary": "Only department has been tested for value-side habits (urine output is charted much more finely in thoracic surgery). The PC key is a finer grouping: rounding habits, analysers and devices may follow the theatre."
        },
        {
          "title": "One PC key, one PC",
          "kind": "assumption",
          "summary": "The register treats each PC key as one recorder PC. Two PCs cabled identically would share a key; the release cannot show it directly, though [[study:hr-delay-groups]] found keys that separate their cases almost perfectly (purity 0.90–0.97), and clock fingerprints could find exceptions."
        },
        {
          "title": "Is a recording without a BIS on the same PC as its key's other recordings?",
          "kind": "assumption",
          "summary": "The assignment assumes that a recording made without a BIS used the same PC as the complete recordings that share its other ports. Validation hides the BIS of recordings that had one, so it cannot test this; a BIS-free recording on a PC kept only for such cases would be assigned to the wrong setup. Clock fingerprints would test it."
        }
      ],
      "figures": [
        {
          "id": "setup-identification",
          "file": "setup-identification.png",
          "theme": "agreement and residuals",
          "source": "vitaldb-bench tools/setup_assign_figure.py",
          "caption": "Left: with one core port hidden in turn from the 5,755 complete recordings, how often the top key is the true one, against its posterior (bins of fewer than 10 left out; marker size grows with the bin). The Solar8000 and Primus ports are almost always recovered; SNUADC and BIS are close to calibrated, with BIS under-confident around 0.45. Right: the 633 recordings without a complete key, by the missing device. A missing BIS is mostly settled; a missing Solar8000 mostly has no matching key.",
          "alt": "Left, a calibration plot of the share correct against the posterior for four devices. Right, horizontal stacked bars of 633 recordings by missing device, coloured by how confidently a key was assigned."
        }
      ],
      "tags": [
        "time side"
      ],
      "derived": {
        "observations": [
          "device-drift-by-setup",
          "oximeter-frame-clock-fingerprint"
        ],
        "code_urls": {
          "vitaldb-bench/tools/setup_assign.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/setup_assign.py",
          "vitaldb-bench/tools/frame_an.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/frame_an.py"
        }
      }
    },
    "shared-bursts": {
      "id": "shared-bursts",
      "name": "Shared bursts between the ECG and the EEG",
      "summary": "Electrocautery and muscle bursts appear in both the SNUADC ECG and the BIS EEG. Cross-correlating their envelopes gives the offset between the two devices' stamps every 10 minutes: about −0.8 s, in a sawtooth that follows both clocks' corrections.",
      "date": "2026-10-12",
      "status": "current",
      "methods": [
        "shared-events"
      ],
      "techniques": [
        "windowed-cross-correlation",
        "null-controls"
      ],
      "clocks": [
        "snuadc-sample",
        "bis-clock"
      ],
      "devices": [
        "snuadc",
        "bis"
      ],
      "variables": [
        "snuadc-ecg-ii",
        "bis-eeg"
      ],
      "data": "A first probe on 30 benchmark cases with BIS EEG, then all 191 benchmark cases with BIS.\n",
      "background": "Two devices on separate clocks need an event both of them record. Broadband bursts (electrocautery, muscle\nactivity) reach the ECG electrodes and the EEG electrodes at the same instant.\n",
      "method": "The envelope is the log power of the ECG at 30–60 Hz and of the EEG at 30–55 Hz, median-filtered over 0.55 s to\nremove the QRS, with a 10 s running median subtracted. Each 10-minute window (stepped by 5 minutes) is\ncross-correlated over ±20 s, and counts as good when r ≥ 0.3 and the peak beats any lag more than 1 s away by at\nleast 0.15. The EEG's two channels give two independent estimates per window; EEG1 against EEG2 is the control.\nOther candidate shared signals were tried: bursts within SNUADC, mains hum, and ventilation.\n",
      "results": "- **Coverage:** 167 of 191 cases have at least one good window, 145 at least 5; 3,663 good windows in all.\n- **Offset:** median −792 ms (5th–95th percentile −1,053 to −533 ms), EEG minus ECG stamp for the same burst.\n- **Precision:** EEG1 and EEG2 agree to 11 ms (median; p90 36 ms); the control sits at 0 ± 7 ms.\n- **A sawtooth, not a constant.** The offset ramps for 30–90 minutes and jumps back. The ramp slope matches the\n  difference of the two devices' drifts from their stamps (r = 0.88; within 25 ppm in 108 of 145 cases).\n- **The jumps are stamp steps:** 79% of 216 lag jumps have a SNUADC or BIS record-stamp step in the same window,\n  against 12% for the same interval 40 minutes later.\n- **Other shared signals fail.** Bursts within SNUADC are rare and point to interference entering after the\n  monitor's processing; mains hum is absent from SNUADC; ventilation is too regular to give a unique lag.\n",
      "discussion": "The first direct, artefact-based confirmation of the clock drifts, and the only measure of the absolute offset\nbetween SNUADC and BIS. Because the ramps and steps are visible in the stamps themselves, they can be corrected\nwithout any artefact ([[study:snuadc-stamp-sawtooth]]); the bursts then check the correction and give the\nconstant left over.\nThese numbers are from the 191-case pilot; over the whole release the constant is −804 ms in step mode and\n−660 ms in frequent-correction mode ([[parameter:eeg-ecg-offset]], [[observation:eeg-ecg-offset-by-mode]]).\n",
      "limitations": "Needs bursts; about one case in eight with BIS has none.",
      "code": [
        "vitaldb-bench/tools/shared_artifacts.py"
      ],
      "figures": [
        {
          "id": "shared-bursts-examples",
          "file": "shared-bursts-examples.png",
          "theme": "example traces",
          "source": "ad hoc (no script in vitaldb-bench yet; to be regenerated by one)",
          "records": [
            750,
            5534,
            5311,
            5780,
            1936,
            5872,
            2778,
            359
          ],
          "caption": "Shared electrocautery bursts show the BIS EEG stamped about 0.75 s earlier than the SNUADC ECG, with ramps set by both clocks’ drift: steep ramps in [[case:750]] and [[case:1936]], small-correction-mode SNUADC in [[case:5311]] and [[case:5780]]. EEG1 and EEG2 agree within about 10 ms.",
          "alt": "Eight scatter plots of EEG lag against hours above three lag histograms."
        },
        {
          "id": "shared-bursts-slopes",
          "file": "shared-bursts-slopes.png",
          "theme": "agreement and residuals",
          "source": "ad hoc (no script in vitaldb-bench yet; to be regenerated by one)",
          "caption": "Lag ramps measured from shared bursts match the drift predicted from the clock corrections (r = 0.88), and 79% of lag jumps fall on stamp steps (191 cases).",
          "alt": "Scatter of measured against predicted ppm with an identity line, and a two-bar chart."
        }
      ],
      "derived": {
        "parameters": [
          "bis-snuadc-rate"
        ],
        "code_urls": {
          "vitaldb-bench/tools/shared_artifacts.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/shared_artifacts.py"
        }
      }
    },
    "snuadc-firmware": {
      "id": "snuadc-firmware",
      "name": "The SNUADC firmware",
      "summary": "The public SNUADC firmware explains the board's timing: it sends no time stamps, samples on its 16 MHz crystal, loses time rather than bunching samples after a stall, reads its channels one after another, and truncates its values. A predicted fault at the microsecond counter's wrap was not found in the data.",
      "date": "2026-10-13",
      "status": "current",
      "techniques": [
        "prediction-test"
      ],
      "methods": [
        "source-review",
        "signal-borne-clocks"
      ],
      "clocks": [
        "snuadc-sample",
        "monitor-output-clock"
      ],
      "devices": [
        "snuadc",
        "vital-recorder"
      ],
      "variables": [
        "snuadc-ecg-ii",
        "snuadc-art",
        "snuadc-pleth"
      ],
      "data": "Source: github.com/vitaldb/snuadc, firmware/SNUADC/SNUADC.ino (an Arduino sketch for an ATmega32U4). The wrap\nprediction was tested on 6 files with the 240 Hz monitor line (17 October).\n",
      "background": "Several measured behaviours (the stamp sawtooth, the signal lost at resets, a sub-millisecond skew between\nchannels) needed a mechanism. The board's firmware is public.\n",
      "method": "The sampling, transmission and calibration code was read for what it predicts about timing and values, and each\nprediction was checked against the measurements in the atlas. One prediction (extra samples at each wrap of the\nmicrosecond counter, every 2³² µs = 4,295 s of uptime) was tested directly: a fine step search on the sample-count\ntimeline of the 240 Hz monitor line (standard error 0.04–0.1 ms) for steps 4,295 s apart.\n",
      "results": "- **No time stamps in the stream.** Each sample is 4 × 3 bytes (two 10-bit channels per 3 bytes) over USB serial.\n  The board sends no time, so the recorder stamps by sample count: the reason the SNUADC sawtooth and the stamp\n  model exist.\n- **Crystal clock.** Sampling is timed by micros() on the 16 MHz crystal, so the long-term rate is the crystal's;\n  crystal tolerance gives the tens of ppm measured against the recorder.\n- **No catch-up after an overrun.** The schedule is us_next = max(us_next + us_wait, now): a late sample resets\n  the schedule instead of sending extra samples, so a stall of more than 2 ms loses time rather than bunching\n  samples. The rate is set by the recorder (500 Hz = 2,000 µs exactly; up to 1,000 Hz).\n- **Channel skew.** The 8 channels (pins A11, A6, A0–A5, in that order) are read one after another, about 0.1 ms\n  each, so the last is read up to about 0.8 ms after the first. Which track each pin carries is not stated.\n- **Value scale.** A 10-bit converter on the internal 2.56 V reference; a two-point calibration in EEPROM maps\n  −4 V and +4 V to counts 102.4 and 921.6; the result is truncated (not rounded) and clamped to 0–1023.\n  Truncation biases values down by half a count (about 0.5 mmHg for arterial pressure). The arterial ceiling near\n  count 917 is the monitor's +4 V output (about 400 mmHg); the ECG clips at 0 and 1023.\n- **Event inputs.** Two event jacks are sent as flag bits with each sample: a possible same-clock marker, if any\n  case used them.\n- **The predicted wrap fault is not seen:** no steps 4,295 s apart, and half the files have no step shared by\n  PLETH and ART in the window where one was due. A few isolated shared steps of 0.2–1.9 ms remain unexplained.\n",
      "discussion": "The stall rule explains the signal lost at each reset ([[study:reset-cluster-order]]): the acquisition pauses\nabout 20 ms and the time is never sampled. The channel skew fits the 0.31–0.39 ms ART–PLETH phase difference at\n240 Hz ([[study:monitor-lines]]). The wrap result means either the per-sample processing time is close to 2 ms, or\nthe deployed firmware differs from the public one.\n",
      "limitations": "Read, not tested on the device. The deployed firmware version is unknown; the channel-to-track mapping is unknown.",
      "open_questions": [
        {
          "title": "Which firmware ran",
          "kind": "phenomenon",
          "summary": "The deployed version, and which converter channel feeds which track, are not known."
        }
      ],
      "references": [
        "snuadc"
      ],
      "derived": {
        "observations": [
          "reset-signal-lost",
          "snuadc-channel-skew",
          "snuadc-event-inputs",
          "snuadc-no-timestamps",
          "snuadc-value-truncation"
        ]
      }
    },
    "snuadc-stamp-sawtooth": {
      "id": "snuadc-stamp-sawtooth",
      "name": "The stamp sawtooth model for SNUADC and BIS",
      "summary": "Fitting each track's record stamps against its sample count as a drift with corrections (a sawtooth) puts SNUADC and BIS on one timeline without any artefact. Checked against shared bursts, the EEG–ECG timing error falls from about 790 ms on the recorded stamps to 33 ms with one constant per SNUADC mode in the pilot, and 42 ms over the whole release.",
      "date": "2026-10-12",
      "status": "current",
      "methods": [
        "stamp-reading",
        "shared-events"
      ],
      "techniques": [
        "sawtooth-fit",
        "windowed-cross-correlation"
      ],
      "clocks": [
        "snuadc-sample",
        "bis-clock",
        "recorder-pc"
      ],
      "devices": [
        "snuadc",
        "bis",
        "vital-recorder"
      ],
      "variables": [
        "snuadc-ecg-ii",
        "bis-eeg"
      ],
      "data": "191 benchmark cases with BIS; validation on 143 cases with at least 5 good burst windows each way, and on 3,683\nten-minute windows in 165 cases. Later refitted on 250 cases with the signal lost at each SNUADC reset. Rerun on\nthe whole release (19 October): all 5,520 recordings with ECG II and BIS EEG; with the adopted model (model3), 36,928 good burst\nwindows in the 2,710 recordings with at least 5.\n",
      "background": "Between corrections a device stamps by its own sample count, so the offset between two devices ramps at the\ndifference of their clock rates; the corrections pull each device back towards the recorder's clock. Both are\nvisible in the stamps alone ([[study:shared-bursts]]).\n",
      "method": "[[software:physiosync]]'s [[code:physiosync/physiosync/stamps.py|stamp_clock]] takes each record's stamp minus the time given by the sample count. It estimates one rate\nper recording from whole teeth (the drift each correction removes, divided by the time since the last), then one\noffset per stretch between real gaps; corrected_times places every sample on that line. VitalDB rules: SNUADC\ncorrects downwards and BIS upwards, and steps the other way are gaps; a SNUADC track in frequent-correction mode\nkeeps its stamps; a step-mode track without a whole tooth takes its port's typical rate (−47 to −74 ppm); a BIS\ntrack without one takes its unit's mean rate. Since 17 October the 17.7 ms lost at each SNUADC step-mode\ncorrection is added back ([[study:paced-ecg]]).\n\n[[figure:validation]]\n",
      "results": "- **Whole teeth** on 151 SNUADC and 184 BIS tracks of 191; the rates agree with the correction scan where that is\n  plausible.\n- **Within-case SD of the EEG–ECG offset:** 137 ms on the recorded stamps, 25 ms on the model (median); the bursts'\n  own noise floor is about 11 ms. Ramps flatten (median slope 53 → 12 ms/h) and lag jumps fall from 1.5 to 0.38\n  per case.\n- **A constant offset is left:** EEG minus ECG −803 ms in step mode (117 cases) and −690 ms in frequent-correction\n  mode (29 cases), tight within a port or unit.\n\n| Correction | Median error | p90 |\n|---|---|---|\n| Recorded stamps | 792 ms | 1,005 ms |\n| Stamps minus one constant | 126 ms | 259 ms |\n| Model minus one constant | 38 ms | 154 ms |\n| Model minus the SNUADC mode's constant (leave-one-out; no artefact needed) | 33 ms | 116 ms |\n| Model minus the case's own constant from its bursts | 16 ms | 67 ms |\n**Whole release (19 October).** The model's ECG–EEG burst check, rerun on every recording\n([[code:vitaldb-bench/tools/burst_validation_agg.py]]):\n\n- **The constants hold:** EEG minus ECG −804.5 ms in step mode (1,530 recordings; interquartile range −835 to\n  −776) and −667.5 ms in frequent-correction mode (78), against −803 and −690 ms on the benchmark.\n- **Where both clocks have whole teeth** (377 recordings) the result matches the benchmark: within-recording SD\n  18 ms, drift left 8.7 ms/h (median absolute slope). Against the step-mode constant, a typical window is off by\n  35 ms (median; p90 135 ms).\n- **Recordings with no SNUADC correction at all** (1,099) were the weak spot. The first model read them as\n  frequent-correction mode and gave the SNUADC no drift, but their EEG–ECG offset drifted by a median of\n  −176 ms/h, the step-mode drift: these are step-mode recordings that end before the first reset.\n- **Giving them their port's step-mode rate fixes most of it** (model2, rerun on every recording,\n  [[code:vitaldb-bench/tools/sa_corrected.py]]). For these 1,096 recordings:\n\n  | | Drift assumed 0 | Port's step-mode rate |\n  |---|---|---|\n  | Within-recording SD (median) | 62 ms | 25 ms |\n  | Drift left (median slope) | −176 ms/h | −11 ms/h |\n  | Drift left (median absolute slope) | 183 ms/h | 74 ms/h |\n  | Error against the mode's constant (median; p90) | 81; 208 ms | 63; 177 ms |\n\n  What is left is spread both ways (p10–p90 −135 to +122 ms/h): each recording's SNUADC drifts at its own rate,\n  not its port's median.\n- **The same for the BIS (model3).** A BIS that makes no correction also got zero drift. Giving it its port's\n  median BIS rate against the recorder PC (+16 to +50 ppm by port, from the correction steps of the full\n  release, never from the bursts) fixes the 236 recordings where that happens:\n\n  | BIS with no correction | Recordings | Drift left (median), model2 → model3 | Within-recording SD |\n  |---|---|---|---|\n  | Shorter than the 8,192 s BIS timer | 202 | −92 → −16 ms/h | 21 → 17 ms |\n  | Longer | 34 | −66 → −14 ms/h | 32 → 28 ms |\n\n  Over all recordings the gain is small (within-recording SD 23.9 → 23.4 ms; median absolute slope 42.6 →\n  40.3 ms/h), because few recordings are affected.\n- Over all 2,707 recordings with model2: a median error of 42 ms against the mode's constant and 18 ms against\n  the recording's own constant (p90 151 and 79 ms); within-recording SD 24 ms (first model: 46, 22, 165, 106 and\n  34 ms).\n- **The adopted model (model3), over all 2,710 recordings with at least 5 good windows:** EEG minus ECG −804 ms\n  in step mode (1,526 recordings; interquartile range −833 to −776), −660 ms in frequent-correction mode (78),\n  and −766 ms with no SNUADC correction (1,099). A median error of 42 ms against the mode's constant (p90 150 ms)\n  and 17 ms against the recording's own constant (p90 79 ms); within-recording SD 23 ms. These are the values in\n  [[parameter:eeg-ecg-offset]] and [[parameter:stamp-model-error]]. (The first bullet above is the first model,\n  before the recordings with no correction were given a rate.)\n\n[[figure:burst-validation-full]]\n\nWhat this looks like in one recording, together with the Primus:\n\n[[figure:sync-demo-stamp-model]]\n",
      "discussion": "The model needs no artefact; the bursts only check it and supply the constant. With the constant for the case's\nSNUADC mode, a typical window lines up to about 42 ms over the whole release (33 ms in the pilot); with the\nrecording's own bursts, to about 17 ms. These are the numbers behind the BIS EEG \"after sync\" timing in the\nplanner.\n",
      "limitations": "Recordings that change correction mode partway, or have many gaps, still fail (p99 about 250 ms). The error\ndistributions come from the bursts, so they include the probe's own noise.\n",
      "open_questions": [
        {
          "title": "Recordings without a SNUADC correction",
          "kind": "next step",
          "summary": "Giving the 1,096 recordings without a SNUADC correction their port's step-mode rate removed most of their drift (median −176 to −11 ms/h), but each still drifts at its own rate (p10–p90 −135 to +122 ms/h left). A per-recording rate cannot come from the stamps: with no correction they carry no information about the recorder PC (the census gives −1.0 ppm, the stamp arithmetic, for all of them). It needs a clock tied to the PC that shares events with the SNUADC (bursts against a BIS with whole teeth, in 122 recordings; calibration, so tested on held-out windows), or the setup's unit. Where the BIS has teeth the bursts imply the SNUADC drifts about −36 ppm rather than −63, the fast-BIS, slow-SNUADC pattern of [[study:device-correction-schedules]]'s open question."
        },
        {
          "title": "Recordings that change correction mode partway",
          "kind": "phenomenon",
          "summary": "Recordings that change correction mode partway still fail the model (p99 about 250 ms). Why a recording changes mode, and how to fit both parts, is open."
        },
        {
          "title": "Why the constant offset differs between modes",
          "kind": "phenomenon",
          "summary": "The EEG lags the ECG by −0.80 s in step mode and −0.66 s in frequent mode after the model (whole release; −0.69 s in the pilot); why the constant differs between modes is not known."
        }
      ],
      "code": [
        "physiosync/physiosync/stamps.py",
        "vitaldb-bench/tools/sa_corrected.py",
        "vitaldb-bench/tools/clock_model_fit.py"
      ],
      "figures": [
        {
          "id": "sync-demo-stamp-model",
          "file": "sync-demo-1216.png",
          "theme": "example traces",
          "source": "vitaldb-bench tools/sync_demo.py",
          "records": [
            1216
          ],
          "caption": "One recording ([[case:1216]], 8.7 hours), before synchronisation (left) and after (right), on the same scales. Top: each clock's time minus the synchronised time. The SNUADC's stamps drift and are pulled back by half a second about every two hours; the BIS's stamps sit about 0.8 s off and are pushed forward every 8,192 s; the Primus drifts about 4 s over the recording and steps at its airway-pressure pauses (dotted lines). After synchronisation the typical error left is about 3 ms (SNUADC), 42 ms (BIS) and 55 ms (Primus, whole-release medians; bands). Middle: the same electrocautery and muscle bursts seen in the ECG and the EEG, as recorded and on the synchronised times, a check independent of the correction. Bottom: the airway-to-central-venous pressure lag of the breath cycle, as recorded and, after, as the error left when each 10-minute window is predicted from all the others.",
          "alt": "Six panels over 9 hours. Left column: sawtooth lines for SNUADC and BIS and a rising line for the Primus spanning about 3.5 seconds; burst lags ramping between minus 0.6 and minus 1.2 seconds; breath lags rising from minus 2 to plus 2 seconds. Right column: the same quantities flat near zero."
        },
        {
          "id": "stamp-model-validation",
          "file": "stamp-model-validation.png",
          "theme": "agreement and residuals",
          "source": "ad hoc (no script in vitaldb-bench yet; to be regenerated by one)",
          "records": [
            4644,
            359,
            2580,
            5780,
            750,
            1703
          ],
          "caption": "The stamp sawtooth model flattens the EEG-minus-ECG offset in [[case:4644]], [[case:2580]], [[case:750]] and [[case:1703]] (step mode) and in [[case:359]] and [[case:5780]] (small-correction mode). With a per-case constant the median timing error falls from 792 ms to 16 ms.",
          "alt": "Six scatter plots of recorded against model stamps beside bars of error by correction level and two histograms."
        },
        {
          "id": "validation",
          "file": "stamp-model-error-by-level.png",
          "caption": "EEG–ECG timing error per 10-minute window at each level of correction (3,683 windows, 165 cases).\n",
          "alt": "Bar chart of median and 90th-percentile timing error for five correction levels, falling from 792 and 1,005 ms for recorded stamps to 16 and 67 ms for the stamp model with a per-case constant.",
          "placeholder": true
        },
        {
          "id": "burst-validation-full",
          "file": "burst-validation-full.png",
          "theme": "agreement and residuals",
          "source": "vitaldb-bench tools/burst_validation_agg.py",
          "caption": "The stamp model checked with ECG–EEG bursts in every recording (19 October). Left: the constant left after the model, per recording, by SNUADC correction mode. Middle: the remaining timing error per 10-minute window, against the mode's constant (leave-one-out) and the recording's own. Right: the drift left over each recording: near zero where both clocks were fitted from whole teeth (blue), and centred near zero, but wider, where the SNUADC made no correction and was given its port's step-mode rate (grey; model3, which also gives a BIS with no correction its port's rate). Before that change the grey group sat near −176 ms/h.",
          "alt": "Three panels; a sharp peak near −805 ms for step mode; two cumulative curves rising between 10 and 200 ms; a slope histogram with a narrow blue peak at zero and wider orange and grey humps also centred at zero."
        }
      ],
      "derived": {
        "parameters": [
          "bis-rate",
          "snuadc-step-rate",
          "eeg-ecg-offset",
          "snuadc-step-period",
          "stamp-model-error"
        ],
        "observations": [
          "eeg-ecg-offset-by-mode",
          "reset-signal-lost",
          "snuadc-no-timestamps"
        ],
        "examples": [
          "ppv-by-breath"
        ],
        "code_urls": {
          "physiosync/physiosync/stamps.py": "https://github.com/metrology-atlas/physiosync/blob/main/physiosync/stamps.py",
          "vitaldb-bench/tools/sa_corrected.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/sa_corrected.py",
          "vitaldb-bench/tools/clock_model_fit.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/clock_model_fit.py"
        }
      }
    },
    "solar-snuadc-clock": {
      "id": "solar-snuadc-clock",
      "name": "The SNUADC clock through the Solar8000 numerics, whole release",
      "summary": "On the SNUADC's own sample count, the delay from the waveform to the PC-stamped Solar8000 heart rate and mean arterial pressure ramps at the SNUADC's drift against the PC, measured without its stamps or resets. In recordings with whole reset intervals it agrees with them (−63 ppm; median difference +1.5 ppm, 581 recordings). It is too noisy per recording, and in recordings under about 4 h its rate is biased toward zero, so it cannot yet speak for recordings without resets.",
      "date": "2026-10-19",
      "status": "current",
      "methods": [
        "shared-events",
        "delays-and-windows"
      ],
      "techniques": [
        "windowed-cross-correlation"
      ],
      "clocks": [
        "snuadc-sample",
        "recorder-pc",
        "solar8000-clock"
      ],
      "devices": [
        "snuadc",
        "solar8000",
        "vital-recorder"
      ],
      "variables": [
        "snuadc-ecg-ii",
        "snuadc-art",
        "solar8000-hr",
        "solar8000-art-numerics"
      ],
      "data": "All 6,388 recordings were read; 50 had no 500 Hz ECG. A rate needs at least 4 usable 30-minute blocks over\n1.5 h: 4,542 recordings for the combined rate (heart rate 4,447; mean arterial pressure 2,969). Reset-interval\nrates for 581 of them come from the clock-network edge table; the shared-burst residuals of the adopted stamp model\n(model3) for comparison.\n",
      "background": "The Solar8000 computes heart rate and mean arterial pressure from the same ECG and arterial pressure that SNUADC\nsamples from its analogue outputs, and the recorder stamps each value on arrival with the PC clock. Within a\nrecording the numeric's delay behind the waveform is fixed ([[study:numeric-delay-variability]]). Placed on the\nSNUADC's sample count, the waveform drifts against the PC at the SNUADC crystal's rate, and the delay ramps with it.\nThe 66-case pilot ([[study:numeric-clock-check]]) gave −65.3 ppm against −66.7 ppm from the stamp model.\n",
      "method": "Two timelines per recording: the sample count (one offset per stretch between gaps, the 17.7 ms lost at each reset\nadded back, rate 0) and the adopted stamp model. On each, the numeric's delay was fitted per 30-minute block on a\n0.05 s grid with the likelihood of [[study:numeric-delay-variability]], and a robust line (Theil–Sen) through the\nblock delays gave the rate; heart rate and mean arterial pressure were combined by inverse variance. Recordings\nwere grouped by SNUADC correction mode, and step-mode recordings by whether they have a whole reset interval.\n",
      "results": "- **Where it can be checked, it closes:** in step-mode recordings with whole reset intervals the median is\n  −63 ppm, and against those recordings' own reset intervals the median difference is +1.5 ppm (p25–p75 −24 to\n  +36; 581 recordings; median |z| 1.11).\n- **Per recording it is noisy:** a median standard error of 40 ppm from mean arterial pressure and 83 ppm from\n  heart rate.\n- **It is biased toward zero in short recordings:** in every group the median rises toward 0 as recordings get\n  shorter (step mode without a whole reset interval: about +10 ppm under 2.5 h, −38 to −46 ppm over 4 h). A 30-minute\n  block ramps by only about 0.11 s at −63 ppm, two steps of the delay grid, and with few blocks the robust line\n  is pulled flat by tied delays and by the first block, which often sits apart.\n- **The burst check disagrees with the short-recording medians:** on the adopted stamp model, the ECG–EEG burst lag\n  in recordings without a whole reset interval has a median slope of −7 ms/h (step mode, 906 recordings) and\n  +10 ms/h (no correction, 521), close to zero, so the port's step-mode rate the model gives them fits on average.\n\n[[figure:solar-snuadc-rate]]\n",
      "discussion": "A third route to the SNUADC's drift, beside the resets and the shared bursts, and the only one that needs neither\nthe SNUADC's stamps nor the BIS. Where resets exist it agrees with them, which closes a loop through the Solar8000.\nIts group medians for shorter recordings are an artefact of the estimator, not evidence that those units run slow;\nthe burst residuals support the stamp model's choice of the port's rate. The question of the long recordings\nthat show no correction ([[observation:snuadc-long-uncorrected]]) remains open.\n",
      "limitations": "Per-recording rates are too noisy to correct a single recording. The delay grid (0.05 s) and the 30-minute blocks\nlimit the estimator in short recordings. The Solar8000's delay occasionally steps; heart rate is truncated and\npolled every 2 s.\n",
      "open_questions": [
        {
          "title": "A finer estimator for short recordings",
          "kind": "next step",
          "summary": "Fitting the delay's slope directly in the likelihood (a delay that ramps within the recording), on a finer grid and without the first block, would remove the bias toward zero and let this route test recordings with no SNUADC reset."
        }
      ],
      "code": [
        "vitaldb-bench/tools/solar_snuadc_clock.py",
        "vitaldb-bench/tools/clock_route_figs.py"
      ],
      "figures": [
        {
          "id": "solar-snuadc-rate",
          "file": "solar-snuadc-rate.png",
          "theme": "population results",
          "source": "vitaldb-bench tools/clock_route_figs.py",
          "caption": "The SNUADC's drift against the recorder PC through the Solar8000 numerics, median by recording length (bins with at least 15 recordings). In recordings with whole reset intervals (blue) it reaches the resets' −63 ppm; in every group the estimate rises toward zero as recordings get shorter, an artefact of the estimator rather than of the clocks.",
          "alt": "Three lines of median rate against recording length from 1.5 to 8 hours. All rise toward 0 ppm for short recordings; the blue line for recordings with reset intervals settles near −63 ppm beyond 5 hours."
        }
      ],
      "derived": {
        "observations": [
          "snuadc-long-uncorrected"
        ],
        "code_urls": {
          "vitaldb-bench/tools/solar_snuadc_clock.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/solar_snuadc_clock.py",
          "vitaldb-bench/tools/clock_route_figs.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/clock_route_figs.py"
        }
      }
    },
    "spo2-latency": {
      "id": "spo2-latency",
      "name": "SpO2 and pulse rate latency from step events",
      "summary": "SpO2 cannot be recomputed from one plethysmogram, so its timing comes from events that change the PLETH abruptly. SpO2 lags the recorded PLETH by about 4 s and then smooths over roughly 10–20 s.",
      "date": "2026-10-12",
      "status": "current",
      "methods": [
        "delays-and-windows"
      ],
      "techniques": [
        "null-controls"
      ],
      "devices": [
        "solar8000"
      ],
      "variables": [
        "solar8000-spo2",
        "solar8000-pleth-hr",
        "snuadc-pleth"
      ],
      "data": "231 cases. Times are relative to the recorded PLETH, itself about 0.4 s behind the finger.",
      "results": "| Event | Events | Result |\n|---|---|---|\n| Probe off (flat PLETH) | 139 | SpO2 blanks 0.6 s after pulse loss (IQR −0.4 to 1.9); first value 5.5 s after return |\n| Low perfusion | 189 | SpO2 mostly freezes rather than blanking (about 55–70% of 5–20 s episodes) |\n| Electrocautery | 3,132 | No effect: 2% reach the PLETH; SpO2 moves in 7% vs 5% at random |\n| PLETH-only artefacts (motion) | 296 | SpO2 moves ≥ 2% after 34%; dip starts +4 s, deepest +8 s, recovers by ~25 s |\n| NIBP cuff inflation | 991 | Not usable: only 4 occluded the oximeter arm |\n\nPLETH_HR blanks and resumes at the same moments as SpO2: one module.\n",
      "discussion": "These events time the oximeter's processing, not a true desaturation. The SpO2 window (10–20 s) is an estimate,\nnot a measurement.\n",
      "code": [
        "vitaldb-bench/tools/spo2_event_pilot.py"
      ],
      "figures": [
        {
          "id": "spo2-event-pilot",
          "file": "spo2-event-pilot.png",
          "theme": "delay and window fits",
          "source": "vitaldb-bench tools/spo2_event_plot.py",
          "caption": "SpO2 is blanked or frozen when the pulse is lost and returns about 5.5 s after the pulse comes back; PLETH artefacts move SpO2 and PLETH_HR within seconds.",
          "alt": "Grouped bars of blanked or frozen fractions, box plots of timings, and two event-averaged response curves."
        }
      ],
      "derived": {
        "parameters": [
          "spo2-onset"
        ],
        "observations": [
          "spo2-freezes"
        ],
        "examples": [
          "gas-gradients-at-the-draw"
        ],
        "code_urls": {
          "vitaldb-bench/tools/spo2_event_pilot.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/spo2_event_pilot.py"
        }
      }
    },
    "stamp-arithmetic": {
      "id": "stamp-arithmetic",
      "name": "SNUADC stamp arithmetic",
      "summary": "Between corrections the SNUADC block stamps run −0.95 ppm against the sample count: each 0.2 s increment is about 0.19 µs short. This, not clock drift, is the \"about −1 ppm\" seen in frequent-correction mode.",
      "date": "2026-10-17",
      "status": "current",
      "methods": [
        "stamp-reading"
      ],
      "clocks": [
        "snuadc-sample",
        "recorder-pc"
      ],
      "devices": [
        "snuadc",
        "vital-recorder"
      ],
      "variables": [
        "snuadc-ecg-ii"
      ],
      "data": "Six short-to-medium files (1.4–2.6 h), chosen at random; consistent with the frequent-correction rows and the\npaced recordings in the rest of the atlas. Then a census of the record stamps of all 6,388 files (17 October;\n[[code:vitaldb-bench/tools/snuadc_stamp_census.py]]): 12,583 stretches of at least 10 minutes between\ncorrections, 19,706 recording hours, in the 6,321 files with SNUADC whose records do not overlap throughout.\n",
      "method": "For each record, the stamp minus the time given by the sample count, e = (stamp − start) × 500 − samples so far,\nwas fitted for slope within each segment between corrections.\n",
      "results": "- **−0.954 ppm in every segment of the six files**, the same in step and frequent-correction mode.\n- **Frequent-correction mode is not drifting.** Its \"about −1 ppm\" of stamps against the count is this\n  arithmetic; the real drift in that mode is under about 1 ppm.\n- **The census holds it, with exceptions:** the median slope is −0.9535 ppm, and 89.5% of the recording hours\n  lie within 0.05 ppm of −0.954. But 747 files (12%) sit elsewhere, from about −0.90 to −0.55 ppm (median\n  −0.85), with no trend through the release.\n- **Stamp-based loaders lose about 1.6 samples per hour** (32,020 in all): one about every 37 minutes, as\n  predicted.\n\n[[figure:slopes]]\n- **The paced recordings agree:** their pacing interval on stamp time and on the sample count differs by\n  0.8–1 ppm ([[study:paced-ecg]]).\n",
      "discussion": "The likely cause is fixed-point arithmetic: a 0.2 s increment held at a resolution of 2⁻²⁰ s. That is unproven.\nThe vitaldb package places blocks by rounding their stamps up to a sample, so it overwrites one sample about every\n35 minutes; placing samples by count, as the [[code:timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/connector.py|TimeNet import]] does, avoids it. Rates read from stamps carry the\n−0.95 ppm, which is negligible beside their spread.\n",
      "limitations": "What sets the slope in the 12% of files that differ is not known (a different recorder build, or a different\nincrement), and the cause of −0.954 itself (fixed-point arithmetic) is unproven.\n",
      "open_questions": [
        {
          "title": "How far does the SNUADC drift in frequent-correction mode?",
          "kind": "conflict",
          "summary": "Between corrections the frequent-mode stamps show only the −0.95 ppm of stamp arithmetic, which is why the real drift in that mode is put under about 1 ppm. But the small forward corrections that keep those stamps on the PC add up to more: about one an hour of +5 to +15 ms ([[parameter:snuadc-frequent-steps]]), and the correction scan's per-recording drift for the 82 frequent-mode recordings it could fit has a median of +2.7 ppm (10th percentile −0.9; [[study:clock-correction-scan]]). The drift against the PC is what the corrections remove, so it may be a few ppm rather than under one. Not reconciled."
        },
        {
          "title": "Why 12% of files run at a different slope",
          "kind": "phenomenon",
          "summary": "747 files sit between −0.90 and −0.55 ppm instead of −0.954, with no trend through the release. A different recorder build or a different stamp increment are the obvious candidates; comparing these files' header version, device list and correction mode with the rest would test it."
        },
        {
          "title": "A fixed-point stamp behind −0.954 ppm",
          "kind": "hypothesis",
          "summary": "A 0.2 s increment held at a resolution of 2⁻²⁰ s fits, but is unproven; the recorder's source or one controlled recording would settle it."
        }
      ],
      "references": [],
      "figures": [
        {
          "id": "slopes",
          "theme": "drift and resets",
          "source": "vitaldb-bench tools/snuadc_stamp_census.py",
          "file": "stamp-census-slopes.png",
          "caption": "Slope of record stamp minus sample count within each stretch between corrections, weighted by recording hours, for every file in the release.",
          "alt": "Histogram of stamp slopes in ppm; a sharp peak at −0.954 holds most recording hours, with a tail towards −0.70."
        }
      ],
      "derived": {
        "parameters": [
          "snuadc-stamp-arithmetic"
        ],
        "observations": [
          "loaders-lose-samples",
          "snuadc-stamp-arithmetic"
        ]
      }
    },
    "step-classification": {
      "id": "step-classification",
      "name": "What each break in the stamps is",
      "summary": "Every step between waveform records in the release, sorted by mechanism: written twice, a SNUADC clock reset, interleaved streams, a pause shared by every device, whole messages, less than a message, or a fraction of a message beyond whole ones. A continuity test with controls shows which classes lose data: shared pauses and most longer SNUADC gaps do; steps under one message, and SNUADC's lone one-message steps, mostly do not.",
      "date": "2026-10-19",
      "status": "draft",
      "methods": [
        "data-continuity",
        "signal-continuity"
      ],
      "techniques": [
        "remainder-test",
        "junction-scores",
        "null-controls"
      ],
      "clocks": [
        "recorder-pc",
        "snuadc-sample",
        "bis-clock",
        "primus-clock"
      ],
      "devices": [
        "vital-recorder",
        "snuadc",
        "primus",
        "bis",
        "cardioq"
      ],
      "variables": [
        "snuadc-ecg-ii",
        "snuadc-pleth",
        "snuadc-art",
        "primus-co2",
        "primus-awp",
        "bis-eeg"
      ],
      "code": [
        "vitaldb-bench/tools/step_classify.py",
        "vitaldb-bench/tools/step_classify_agg.py",
        "vitaldb-bench/tools/step_join_null.py"
      ],
      "data": "All 6,388 recordings; every non-zero step between consecutive records of every waveform track (backward steps\ncapped at 3,000 per track, so backward counts are lower bounds; the number of recordings is exact). Controls: 40\nrandom recordings, up to 200 ordinary junctions per track.\n",
      "background": "[[study:record-messages-and-steps]] found far more backward steps than gaps, and gaps that are mostly not whole\nmessages. A step in the stamps can mean data lost, data written twice or out of order, or a clock correction\nthat moved the stamps and lost nothing. Each leaves a different trace in the stamps and in the samples either\nside.\n",
      "method": "- For every step: its size in messages and its remainder; whether record k+1 is identical to record k; whether\n  it continues record k−1 better than record k; whether another device has a gap of over 1 s within 1 s; and\n  **join_z**, the distance of the first sample after the step from a linear extrapolation of the last samples\n  before it, in units of the record's own median one-step prediction error.\n- Classes, first match wins. Backward: **duplicate** (identical record); **reset** (SNUADC consecutive backward\n  steps that together step back 0.45–0.52 s: one step, or the five records written out of order at a reset,\n  [[observation:reset-records-out-of-order]]); **interleave** (continues record k−1, in a track with at least 50\n  such steps); **other backward**. Forward: **shared** (over 1 s, with another device stepping at the same\n  moment); **whole** (a whole number of messages, within half a sample); **sub-message**; **fractional**.\n- **Controls for join_z.** At ordinary junctions (no step) and with one message cut out at the same junctions,\n  in 40 random recordings. If a class loses no data, its share of join_z < 3 should match the first control; if\n  it loses a message, the second.\n",
      "results": "Counts in SNUADC ECG II (recordings in brackets); the other SNUADC channels are alike.\n\n| class | steps | recordings |\n|---|---:|---:|\n| duplicate | 21,006 | 8 |\n| reset | 15,988 | 2,546 |\n| interleave | 34,431 | 24 |\n| other backward | 5,364 | 500 |\n| shared pause | 800 | 717 |\n| whole messages | 554 | 381 |\n| sub-message | 1,168 | 316 |\n| fractional | 3,683 | 2,425 |\n\n- **Backward steps are three things.** Duplicates and interleaving are confined to a few dozen recordings\n  ([[observation:duplicated-records]], [[observation:interleaved-records]]). Everywhere else SNUADC's backward\n  steps are its step-mode clock resets: 2,546 recordings, either one −0.49 s step or five records stepping\n  0.12 / 0.08 s that add up to it. About 500 recordings keep a few unexplained backward steps.\n- **The continuity test separates the classes.** At ordinary junctions join_z < 3 in 92–95% of SNUADC steps;\n  with one message cut out, in 12–24%.\n  - Shared pauses and SNUADC fractional gaps lose data: shares near the cut-out control (SNUADC fractional 35–42%;\n    about 70% of them lose samples).\n  - SNUADC sub-message steps (2–20 ms, the frequent-mode corrections) and its lone one-message steps run on as\n    if nothing were missing (88–95%). The one-message steps tend to fall where the signal moves to the mid-scale\n    no-signal level.\n  - BIS sub-message steps run on (93%, like the control): the clock resynchronisations of\n    [[study:continuity-gaps-segments]] lose nothing. BIS fractional gaps (mostly 0.13–0.5 s) sit between the\n    controls (73%): roughly a third lose data.\n  - Primus AWP gaps of whole messages (80% of them 20–23 s, in steps of one message) run on almost like the\n    control (86% against 88%), yet the test is weak for AWP, whose 0.48 s records are often nearly flat. A\n    breath-level test against CO2 shows these gaps do lose about 21 s of AWP ([[observation:primus-awp-pauses]]):\n    for AWP, join_z cannot tell.\n- Seconds missing over the release in ECG II: about 196,700 s in fractional gaps, 70,200 s in shared pauses,\n  5,100 s in whole-message steps.\n\n[[figure:step-classes]]\n",
      "discussion": "The stamps alone say what kind of break each step is, and the samples say, on average, whether data were lost.\njoin_z judges a single step only weakly: losing a few samples, or a message on a flat stretch, does not show.\nPer class, against the controls, it is clear. For a reader of the data: backward steps need reordering (resets,\ninterleaving) or deduplication, not gaps; sub-message steps are clock corrections; shared pauses and most SNUADC\ngaps beyond one message are real gaps.\n",
      "limitations": "- The controls come from 40 recordings; join_z's power depends on how steep the signal is near the step.\n- Losses of a few samples (the SNUADC sub-message steps) are below what join_z can see;\n  [[observation:small-snuadc-gaps]] found from beat intervals that sub-second SNUADC gaps lose data, which this\n  test can neither confirm nor rule out at 2–20 ms.\n- Backward steps were capped at 3,000 per track in the scan.\n",
      "open_questions": [
        {
          "title": "The remaining backward steps",
          "kind": "next step",
          "summary": "About 500 recordings per SNUADC channel and 3,850 for Primus AWP have backward steps none of the classes explains, mostly of a few samples."
        },
        {
          "title": "Fit the missing time",
          "kind": "next step",
          "summary": "For gaps that lose data, fit how many samples are missing ([[method:signal-continuity]]) rather than testing only whether any are."
        }
      ],
      "figures": [
        {
          "id": "step-classes",
          "file": "step-classes.png",
          "theme": "data continuity",
          "source": "vitaldb-bench tools/step_classify_agg.py",
          "caption": "Left: forward steps between records by class, log scales; dotted line at one message. Right: the share of each class the waveform runs across (join_z < 3), against two controls: ordinary junctions with no step (solid line) and the same junctions with one message cut out (dashed). Sub-message and lone one-message steps sit at the no-loss control; shared pauses at the loss control; SNUADC fractional gaps close to it.",
          "alt": "Six rows; left, histograms of forward steps coloured by class; right, bar charts of the share of steps with join_z under 3, with two vertical reference lines."
        }
      ],
      "derived": {
        "observations": [
          "primus-awp-pauses"
        ],
        "code_urls": {
          "vitaldb-bench/tools/step_classify.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/step_classify.py",
          "vitaldb-bench/tools/step_classify_agg.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/step_classify_agg.py",
          "vitaldb-bench/tools/step_join_null.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/step_join_null.py"
        }
      }
    },
    "time-zeros": {
      "id": "time-zeros",
      "name": "Time zeros and loaded subsets",
      "summary": "A VitalDB time \"from the start\" depends on which tracks were loaded: loading a subset moves the zero to that subset's first record, seconds or hours later. The arrhythmia database's annotations count from the full file's zero.",
      "date": "2026-10-16",
      "status": "current",
      "methods": [
        "stamp-reading",
        "source-review"
      ],
      "techniques": [
        "beat-matching"
      ],
      "clocks": [
        "recorder-pc"
      ],
      "devices": [
        "vital-recorder",
        "snuadc"
      ],
      "variables": [
        "snuadc-ecg-ii"
      ],
      "data": "The VitalDB Arrhythmia Database (PhysioNet 1.0.0; 482 cases, about 20 minutes of annotated ECG each, R peaks on a\n1/360 s grid) against R peaks detected in SNUADC ECG II; work of the R-peak timing session, consolidated here.\n",
      "method": "Beat-train alignment (impulse trains in 10 ms bins, FFT cross-correlation over ±120 s, beats matched within\n150 ms, a linear trend for drift) gave each case's offset between annotations and detected beats.\n",
      "results": "- **A constant offset per case:** median −1.55 s (5–95% −4.5 to −0.4 s). In 472 of 482 cases it equals minus the\n  ECG track's start delay after the file's first record, to within 1.5 ± 1.2 ms.\n- **So the annotations count from the full-file zero.** The file's zero is its first record of any track (the\n  EVENT track in every file checked); loading only some tracks, in the vitaldb package or in our reader, moves it.\n- **After removing it,** the median case matches 98.9% of annotations within 150 ms, with 0.2 ppm drift.\n- **The annotations follow the stamps, not the sample count:** three cases with a stamp jump before the annotated\n  window keep the rule to within 1–33 ms.\n- **The other residuals are fiducials, not time-base faults:** detector switching between two points in the QRS,\n  or where annotators placed beats. Two constant shifts and three late-starting recordings remain unexplained.\n",
      "discussion": "Times taken from different track subsets share an axis only in absolute file time. The [[code:timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/connector.py|TimeNet import]] reads whole\nfiles, so nothing in the builds moves. Beat-train alignment recovers offsets to about 1 ms, and serves as an\nintegrity check and as a second cross-device method; the time-zero convention belongs in a reporting checklist.\n",
      "derived": {
        "observations": [
          "fiducial-quantisation",
          "loaded-subset-time-zero",
          "snuadc-one-clock"
        ]
      }
    },
    "timestamp-clock-reconstruction": {
      "id": "timestamp-clock-reconstruction",
      "name": "Charted-time clock reconstruction",
      "summary": "The absolute clock time of every event can be recovered from the admission date, which makes minute-of-hour distributions possible despite de-identification.\n",
      "date": "2026-10-13",
      "status": "current",
      "methods": [
        "clinical-time",
        "value-resolution"
      ],
      "background": "- All times are in seconds relative to case start, the recording start set to 0 for de-identification. Absolute clock times are removed.\n- Admission is stored as a date, so its value is the time from midnight to recording start. That gives the clock time of recording start, and from it the clock time of every event.\n",
      "data": "- `clinical_data.csv`: 6,375 cases with valid admission dates.\n",
      "method": "- Checked that −adm is a whole number of minutes from midnight to recording start.\n- Checked where reconstructed recording starts fall in the day, and whether charted events land on 5-minute clock marks.\n",
      "results": "- −adm is a whole number of minutes in 6,373 of 6,375 cases. The exceptions, [[case:1342]] and [[case:5723]], are flagged `clock_uncertain`.\n- Reconstructed recording starts cluster at 07:00–07:59 (1,634 cases) and run through the working day: 07:40 to 18:53 (5th–95th percentile), median 11:33.\n- Op start falls on a 5-minute clock mark in 92% of cases, op end in 91% and anaesthesia end in 95%; anaesthesia start in only 52%.\n",
      "discussion": "- The reconstruction holds, so the charted event times can be modelled on clock minutes as in the digit-preferencing manuscript.\n- The API needs the case's admission date and recording start or end to place a charted time on the clock.\n\nThe same question from the timing side: [[study:case-clock]].\n",
      "limitations": "- Admission and discharge are whole minutes from recording start, but only 1.5% are whole minutes from op start. They seem to have been computed from a recording start truncated to the minute.\n- 93 charted times (92 op ends, 1 op start) do not share the sub-minute offset of the other charted times in their case, and are flagged `clock_offset_mismatch`.\n",
      "tags": [
        "from the resolution work"
      ],
      "derived": {}
    },
    "timestamp-derived-intervals": {
      "id": "timestamp-derived-intervals",
      "name": "Bias in charted intervals",
      "summary": "Charted intervals are biased because each end is rounded with a different anchor, and their per-case uncertainty is large relative to short intervals.\n",
      "date": "2026-10-13",
      "status": "current",
      "methods": [
        "clinical-time",
        "value-resolution"
      ],
      "background": "- Anaesthesia end is recorded at the next mark (ceiling), op end at the previous one (floor), and both starts at the nearest mark.\n",
      "data": "- Per-case posterior means from the four timestamp models in `clinical_data.csv`.\n",
      "method": "- Each interval is the difference of two event posteriors, assumed independent given the device references.\n",
      "results": "- Operation duration is charted about 2.5 min short.\n- Anaesthesia duration is charted about 4.4 min long and emergence about 6.3 min long.\n- Induction to incision is nearly unbiased (−0.5 min).\n- The median posterior SD of an interval is 1.8–2.8 min, and the 90th percentile is 2.4–4.9 min. For a 15-min emergence, that is a large share of the value.\n- Among positive intervals that are whole multiples of 5 minutes, the share on multiples of 15 is 32.5–33.3%\n  (33.3% expected) and on multiples of 30 is 16.6–19.0% (16.7% expected) for anaesthesia duration, operation\n  duration and induction to incision. Only anaesthesia duration has a small excess at 30 minutes, consistent\n  with the heaping of anaesthesia end at :00 and :30. Intervals add no heaping of their own.\n",
      "discussion": "- The API returns intervals as samples from the two event posteriors (`derived_samples`), not as charted differences.\n- Cases with an excluded end return the status, not a distribution.\n",
      "limitations": "- The two event posteriors are assumed independent given the device references.\n",
      "tags": [
        "from the resolution work"
      ],
      "derived": {
        "examples": [
          "oliguria-labels",
          "regression-with-rounding",
          "urine-output-per-kg-per-hour"
        ]
      }
    },
    "timestamp-imputed-and-conformance": {
      "id": "timestamp-imputed-and-conformance",
      "name": "Imputed times, conformance failures and exclusion rules",
      "summary": "About one in twelve op ends was set from the recording rather than charted, and a small set of times fail basic conformance checks; all are encoded as exclusion rules.\n",
      "date": "2026-10-13",
      "status": "current",
      "methods": [
        "clinical-time",
        "value-resolution"
      ],
      "background": "- Vital Recorder stops recording 10 minutes after the heart-rate and SpO2 signals disappear.\n- Imputed times measure when monitoring stopped, not when surgery ended, and would bias any accuracy analysis towards agreement with the signals.\n",
      "data": "- All charted times and dates in `clinical_data.csv`. The machine-readable rules are in `exclusion_rules.json`.\n",
      "method": "- Checked gaps from op end to recording end, whole-second and whole-minute structure, event order, extreme values and placeholder dates.\n",
      "results": "- 537 op ends (8.4%) fall 598–604 s before the end of the recording; most often 601 s (346 cases) or 602 s (129 cases). They are excluded as `imputed`.\n- Anaesthesia ends derived from those op ends are stored with floating-point noise (e.g. 599.999999 s) and sit an exact multiple of 5 minutes after op end: 300 s (9 cases), 600 s (35), 900 s (9), 1,200 s (9) and others.\n- Two dates default to 1900: [[case:4476]] (anaesthesia end) and [[case:1563]] (admission and discharge), near −3.69 × 10⁹ s.\n- Op end comes after anaesthesia end in 52 cases and equals it in 108.\n- Anaesthesia end is +720 min from recording end in [[case:3980]] (12-hour error?) and +1,441 min in [[case:4959]] (date error?).\n- No case has op start before anaesthesia start, op end before op start, or anaesthesia end before anaesthesia start.\n- Op end equals recording end minus 10 minutes (598–604 s) in 537 of 6,388 cases (8.4%). By department:\n  thoracic 16.6% (184 of 1,111), general 6.9% (341 of 4,930), gynaecology 3.5%, urology 3.4%. By anaesthesia:\n  sedation 41.7% (30 of 72), spinal 30.0% (82 of 273), general 7.0%. By operation type it ranges from 15.2%\n  (minor resection) to 2.3% (breast). Emergency 9.8%, elective 8.2%. ([[observation:imputed-op-ends]])\n",
      "discussion": "- Anaesthesia end was most likely charted as an interval after op end and inherited the imputed op end.\n- Excluded values return a status rather than a distribution; flagged values are fitted and returned with the flag.\n- The 1900 dates are the \"missing time stored as zero\" bug from §5.5 of the review.\n",
      "limitations": "- The derived anaesthesia ends were missed in an earlier fit, because their noise (about 10⁻⁶ s) slipped under the test threshold.\n",
      "open_questions": [
        {
          "title": "Was op end imputed deliberately?",
          "kind": "next step",
          "summary": "Was op end imputed from recording end deliberately? This is to be asked of the VitalDB team."
        },
        {
          "title": "Why imputed op ends cluster in thoracic and non-general cases",
          "kind": "phenomenon",
          "summary": "Imputed op ends are twice as common in thoracic surgery as in general surgery, and four to six times as common under sedation or spinal anaesthesia as under general anaesthesia. Cases without a charted op end are not spread at random; why these groups lack one (a different charting workflow, a different person recording, the operation's end not being marked) is not known, and the VitalDB team could say."
        }
      ],
      "tags": [
        "from the resolution work"
      ],
      "derived": {
        "observations": [
          "imputed-op-ends"
        ]
      }
    },
    "timestamp-lab-times-meaning": {
      "id": "timestamp-lab-times-meaning",
      "name": "What lab timestamps mark",
      "summary": "Merged into \"What the lab timestamp marks\" on 18 October 2026; kept so old links still work.\nThe original finding: central-lab timestamps most likely mark specimen receipt in the lab, about 10 minutes after the draw.\n",
      "date": "2026-10-13",
      "status": "superseded",
      "methods": [
        "clinical-time",
        "value-resolution"
      ],
      "background": "- Lab times are machine timestamps at 1 s resolution, with no rounding to model. The seconds component is spread evenly (about 1.7% per second).\n- The working hypothesis was that they mark when the result was returned.\n",
      "data": "- `lab_data.csv`: 131,942 unique timestamps.\n- Intra-operative central-lab timestamps (to 30 min after anaesthesia end) with a blood gas in the preceding hour: CBC 1,665, coagulation 1,539, chemistry 1,613.\n",
      "method": "- Compared timestamps of panels run on different analysers in the same case.\n- Checked for a consistent order of draw between panels.\n- Measured the lag from a point-of-care blood gas to the next central-lab panels.\n- Checked the time of day of ward CBCs, and the minute of the hour for heaping.\n",
      "results": "- Each panel has one timestamp for all its analytes. Panels drawn together are stamped within seconds of each other (75% of CBC–chemistry pairs within 60 s), centred on zero and in no fixed order.\n- There is no consistent order of draw: each panel is equally likely to be first.\n- Central-lab panels after a blood gas are stamped a median of about 10 min later (about a quarter within 5 min and three-quarters within 15 min). The lag is the same for CBC, coagulation and chemistry.\n- Ward CBC timestamps peak at 08:00–09:00, after a typical early-morning phlebotomy round.\n- The minute of the hour varies smoothly, with no spikes at 0 or 5.\n",
      "discussion": "- Result times would differ by analyser turnaround, with chemistry later than CBC. They do not, which rules that out.\n- For the API, lab times are single resolution (1 s, anchor floor). A central-lab timestamp should carry a backward lag of about 10 min (95% within about 30 min) to the sampling time; a blood-gas timestamp needs a few minutes at most.\n\nThe same question from the timing side: [[study:lab-timestamp-meaning]].\n",
      "limitations": "- The conclusion assumes that a blood gas and central-lab tubes are often drawn together.\n",
      "open_questions": [
        {
          "title": "Receipt hypothesis against flush artefacts",
          "kind": "next step",
          "summary": "The receipt hypothesis should be checked against arterial-line flush artefacts in the waveforms, or by asking the VitalDB team."
        }
      ],
      "tags": [
        "from the resolution work"
      ],
      "derived": {
        "superseded_by": [
          "lab-timestamp-meaning"
        ]
      }
    },
    "track-value-resolution": {
      "id": "track-value-resolution",
      "name": "Value resolution of every signal",
      "summary": "How finely every waveform and numeric track in the release records its values. The SNUADC waveforms are 10-bit codes, though VitalRecorder's documentation describes the SNUADC as 12-bit; Primus waveforms use about 10 bits of 12; BIS EEG is a 16-bit grid of 0.05 µV. Numerics are 32-bit floats on decimal grids of 1, 0.1, 0.01 or 0.001, one per track; settings sit on coarser grids of 10 or 100.",
      "date": "2026-10-19",
      "status": "draft",
      "methods": [
        "value-resolution"
      ],
      "devices": [
        "snuadc",
        "primus",
        "bis",
        "cardioq",
        "solar8000",
        "orchestra",
        "ev1000",
        "vigileo",
        "vigilance"
      ],
      "variables": [
        "snuadc-ecg-ii",
        "snuadc-art",
        "snuadc-pleth",
        "primus-co2",
        "primus-awp",
        "bis-eeg",
        "bis-index",
        "solar8000-hr",
        "solar8000-spo2",
        "solar8000-art-numerics",
        "solar8000-nibp",
        "primus-numerics",
        "pump-rates",
        "ev1000-art-mbp",
        "vigileo-sv",
        "vigilance-co"
      ],
      "code": [
        "vitaldb-bench/tools/signal_events.py",
        "vitaldb-bench/tools/signal_events_agg.py",
        "vitaldb-bench/tools/record_scan.py",
        "vitaldb-bench/tools/numeric_agg.py"
      ],
      "references": [
        "vitaldb-docs-devices",
        "vitaldb-docs-config",
        "vitaldb-docs-fileformat"
      ],
      "data": "All 6,388 recordings: every sample of every waveform track (a full histogram of stored values per track) and\nevery record of every numeric track (each distinct value with its count).\n",
      "background": "A .vital file stores each track in a declared format with a gain and offset: measured value = offset + stored\nvalue × gain ([[reference:vitaldb-docs-fileformat]]). Waveforms from converters are stored as integer codes;\nnumerics, and waveforms relayed from devices that send numbers, as 32-bit floats. The format says how many bits\nare available, not how many the device used: that has to be read from the values.\n",
      "method": "- **Waveforms.** One bin per stored code over the whole release. The resolution is the spacing of the codes; the\n  bits to store the observed span are the whole number of bits that hold every code between its ends. The range is taken between the 10⁻⁷ and 1 − 10⁻⁷\n  quantiles of the samples, widened to any code within 64 steps of the body, so that a handful of corrupt samples\n  cannot set it. The spacing is also read in 8 bands of equal sample count, which shows a scale that is not\n  uniform.\n- **Numerics.** The share of records on each decimal grid (100, 10, 5, 1, 0.5, … 0.001), allowing a few units of\n  float32 rounding (a stored 0.1 reads 0.099999…). The resolution is the coarsest grid holding 99% of records;\n  the share on the next coarser grid shows heaping or a mixed resolution (by chance, about 1/2, 1/5 or 1/10).\n",
      "results": "**Waveforms.** Two quantities are kept apart. The *observed span* is what the data show: the codes between the\nlowest and highest values the signal reaches (on the step it uses), and the whole number of bits needed to store\nthat span. The *converter resolution* is an estimate of the device: it can be no less than the observed span, and\nit is stated only with its evidence (all codes of a power-of-two range used, a documented value, or rare values\nat the edge of a larger range).\n\n| Track | Stored as | Step | Observed span (codes used) | Bits to store the span | Converter resolution (estimate) | Evidence |\n|---|---|---|---|---:|---|---|\n| SNUADC ECG II, V5 | uint16 | 0.00987 mV | 0–1023 (all 1,024) | 10 | 10 bits | every code of 0–1023 used, none above; documented as 12-bit |\n| SNUADC ART, CVP, FEM | uint16 | 0.987 mmHg | 0–983 (848) | 10 | 10 bits | same converter as ECG |\n| SNUADC PLETH | uint16 | 0.395 | 0–1023 (852) | 10 | 10 bits | same converter as ECG |\n| Primus CO2 | uint16 | 0.1 mmHg as stored (0.075 fits) | 0–1564 (1,530); 8 samples to 4007 | 11 | probably 12 bits | rare values up to 4046 in CO2 and AWP, inside 0–4095 |\n| Primus AWP | uint16 | 0.1 mbar | 0–1032 (1,010); 22 samples to 4046 | 11 | probably 12 bits | as CO2 |\n| BIS EEG1, EEG2 | float32 | 0.05 µV | −1476.70 to +1800.05 (all 65,536); 400 corrupt samples | 16 | 16 bits | every code of a 2¹⁶ range used, none outside it |\n| CardioQ FLOW | float32 | 1, 2 or 4 (segmented) | 0–1016 (509 values) | 9 as codes (10 as values) | unknown | a 9-bit segmented code ([[observation:cardioq-flow-segmented]]) |\n| CardioQ ABP | float32 | 1 mmHg | −100 to 414 (497); 2 corrupt samples | 10 | unknown, at least 10 bits | whole mmHg, a display resolution rather than a converter |\n\n- **SNUADC is 10-bit in the data, 12-bit in the documentation.** VitalRecorder's device list describes the SNUADC\n  as an 8-channel 12-bit, 500 Hz converter ([[reference:vitaldb-docs-devices]]). Every SNUADC channel in the\n  release stores codes 0–1023 and nothing above, so the stored values have 10 bits whatever the converter\n  delivers ([[observation:snuadc-ten-bit-codes]]).\n- **One volt scale behind every SNUADC channel.** The SNUADC is configured with one gain per channel, from volts to\n  the physical unit ([[reference:vitaldb-docs-config]]). The stored gains are that gain times one fixed step of\n  0.0098746 V per code: ECG gain 1, ART, CVP and FEM gain 100, PLETH gain 40, and the offsets are −4.956 V times\n  the same gain. The converter therefore spans −4.956 to +5.156 V in 1,024 codes.\n- Primus waveforms are stored in 16 bits but use about 10; their few samples near code 4,000 fit a 12-bit device\n  range (0–4095). Their stored gains are not all right: against the Primus's own numerics, CO2 fits only 0.075\n  mmHg per code and AWP only 0.1 mbar per code, so most recordings overstate CO2 by a third\n  ([[observation:primus-waveform-gains]]).\n- The earlier summary counted 54.9 bits for BIS EEG and 16.5 for CardioQ ABP, set by a few corrupt samples;\n  with the robust range the observed spans need 16 and 10 bits.\n\n**Numerics** (the full table, 150 tracks, is in the work folder: `numeric_resolution.csv`)\n\n| Resolution | Tracks |\n|---|---|\n| 100 | Primus fresh-gas flow setting; N2O flow and N2O fractions (almost always 0) |\n| 10 | FiO2 and tidal volume settings (Primus and Solar8000) |\n| 1 | Solar8000 HR, pulse rate, SpO2, ART, CVP, PA, NIBP, ETCO2, FiO2, ventilator pressures and RR; Primus ETCO2, FiO2, pressures, RR, TV, gas flows; EV1000 / Vigileo SV, SVI, SVR; Vigilance volumes and SvO2; INVOS |\n| 0.1 | Temperature; ST segments; minute volume; MAC and agent concentrations (kPa); compliance; inspiratory time; BIS index, SQI and SR; CO and CI (EV1000, Vigileo, Vigilance, CardioQ); EV1000 SVV; Orchestra target concentrations (CT) and the rates of most drugs |\n| 0.01 | BIS SEF, EMG and total power; Primus TV setting (L, so 10 mL); Solar8000 gas 2 (94% on 0.1) |\n| 0.001 | Orchestra volumes, plasma and effect-site concentrations (CP, CE), and the propofol and remifentanil rates |\n\n- Each track has one resolution throughout; none mixes grids except Solar8000 gas 2 (94% on 0.1, the rest on\n  0.01) and Solar8000 FiO2 / FeO2 (0.08% off whole percent).\n- The same quantity from two devices agrees: Primus and Solar8000 minute volume (0.1 L/min), TV (1 mL), and the\n  TV setting (0.01 L on the Primus, 10 mL on the Solar8000).\n- Corrupt values appear in a few tracks: BIS SQI to −2.1 × 10⁸, BIS index to −1,333, Orchestra concentrations and\n  volumes to 10⁶, Primus mean airway pressure 4,000. They will be listed with the leads-off and no-signal codes.\n",
      "discussion": "Recorded values are as fine as the stored codes or the decimal grid, not as fine as the storage format. For\nwaveforms, the step is the quantisation of the measurement (uniform within ±½ step, 1 SD = step/√12; truncated\nrather than rounded in the SNUADC, [[observation:snuadc-value-truncation]]). For numerics, the grid is the\ndevice's display resolution; whether the device rounded or truncated to it is known only for the Solar8000\nheart rate (truncated) and respiratory rate (rounded) ([[observation:hr-truncated]]).\n",
      "limitations": "- The documentation describes the current VitalRecorder; the release was recorded with earlier versions and\n  SNUADC hardware whose resolution may differ from the documented one.\n- The decimal-grid test cannot tell whether a float numeric started as an integer count on a finer scale (a\n  device's own converter); that is a separate question.\n",
      "open_questions": [
        {
          "title": "Why 10 bits from a 12-bit converter?",
          "kind": "hypothesis",
          "summary": "Either the SNUADC used in the release was a 10-bit design, or a 12-bit converter's readings are reduced to 10 bits before they are sent (which the observed truncation would fit). The SNUADC firmware or the VitalDB team could settle it."
        },
        {
          "title": "Rounding or truncation in each numeric",
          "kind": "next step",
          "summary": "Against the Primus's own waveforms (with the corrected gains), all 6,342 recordings favour rounding for ETCO2 (where round and floor differ, 77% of numerics match round, 18% floor), PIP (61% / 32%) and mean airway pressure (62% / 28%). The match is not complete because the window does not reproduce the device's own computation, so the result is \"likely\". The Solar8000 ART numerics cannot be tested this way: the SNUADC copy of the arterial pressure differs from them by more than one step."
        },
        {
          "title": "Integer codes behind float signals",
          "kind": "next step",
          "summary": "Signals sent as floats (BIS EEG, CardioQ, the numerics) may all start as integers from a converter of n bits, scaled once. Recover the integer and the scale for each (BIS EEG already fits 16 bits of 0.05 µV; CardioQ FLOW looks companded)."
        }
      ],
      "derived": {
        "observations": [
          "cardioq-flow-segmented",
          "primus-waveform-gains",
          "snuadc-ten-bit-codes"
        ],
        "code_urls": {
          "vitaldb-bench/tools/signal_events.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/signal_events.py",
          "vitaldb-bench/tools/signal_events_agg.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/signal_events_agg.py",
          "vitaldb-bench/tools/record_scan.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/record_scan.py",
          "vitaldb-bench/tools/numeric_agg.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/numeric_agg.py"
        }
      }
    },
    "value-bolus-drug-doses": {
      "id": "value-bolus-drug-doses",
      "name": "Bolus drugs as unit-dose counts",
      "summary": "Most bolus drug totals are exact counts of standard doses, not rounded estimates; propofol is the exception, and zeros are ambiguous.\n",
      "date": "2026-10-13",
      "status": "current",
      "methods": [
        "value-resolution"
      ],
      "background": "- Nine bolus drug fields: propofol, midazolam, fentanyl, rocuronium, vecuronium, ephedrine, phenylephrine, epinephrine and calcium.\n- The drug fields have no missing values, so 0 means \"none given or none recorded\".\n",
      "data": "- Ephedrine 3,211 non-zero cases; calcium chloride 868; fentanyl 1,168; phenylephrine 844; rocuronium 5,942; propofol 2,335.\n- Midazolam, vecuronium and epinephrine have 80, 12 and 89 non-zero cases.\n",
      "method": "- For each drug, found the unit the values sit on and the most common totals.\n- Checked rocuronium against case duration and weight, and propofol against weight.\n- Checked zeros against anaesthesia type.\n",
      "results": "- Ephedrine sits on 5 mg (99.4%): a count of 5 mg boluses.\n- Calcium chloride sits on 300 mg (90%) or 150 mg (97%): a count of ampoules, sometimes half.\n- Fentanyl sits mostly on 50 mcg; 100 mcg is the most common total.\n- Phenylephrine sits on 10 mcg (99%): a sum of small boluses.\n- Rocuronium sits on 10 mg (95%): an intubating dose plus top-ups. The total rises with case duration (Spearman 0.61), barely with weight (0.12).\n- Rocuronium is 0 in all 72 sedation cases, in 261 of 273 spinal cases, and in 113 general anaesthetics.\n- Propofol bolus is 0 in 3,719 of 6,043 general anaesthetics, many probably given by infusion (TIVA).\n",
      "discussion": "- Agreed: unit-count drugs (ephedrine, calcium chloride, fentanyl, phenylephrine) are exact, with the unit dose as resolution; values off that unit are flagged `off_grid`.\n- Rocuronium is exact on a 10 mg resolution.\n- Midazolam, vecuronium and epinephrine get native resolution only.\n- A 0 in any drug field returns status `zero_or_not_recorded` and no distribution.\n- Propofol behaves like a rounded estimate and has its own fitted model (`value-model-intraop-ppf`).\n",
      "limitations": "- Zeros cannot be told apart from drugs given by another route, such as a propofol infusion on the pump tracks.\n",
      "tags": [
        "from the resolution work"
      ],
      "derived": {
        "examples": [
          "bolus-response-latency"
        ]
      }
    },
    "value-intraop-colloid-containers": {
      "id": "value-intraop-colloid-containers",
      "name": "Colloid container rules",
      "summary": "Colloid is a mix of products given in whole containers that depend on the operation, so values are treated as container counts rather than rounded volumes.\n",
      "date": "2026-10-13",
      "status": "current",
      "methods": [
        "value-resolution"
      ],
      "background": "- The first screen showed that 500 mL bags dominate, with an odd peak at 35 mL.\n- Colloid use varies by department (urology 27%, gynaecology 18%, general surgery 9%, thoracic 3%) but not over time.\n",
      "data": "- 6,388 values, none missing, 5,857 zeros; 531 non-zero (8%), up to 1,900 mL.\n- Colloid cases are long and bloody (median 5.3 h, EBL 600 mL, against 2.8 h and 100 mL).\n",
      "method": "- Split the non-zero values by operation: liver transplant, kidney surgery (kidney transplant, donor and other nephrectomy) and other surgery.\n- Matched each group's values to plausible container sizes.\n",
      "results": "- Other surgery (339): dominated by 500 mL (202) and 1,000 mL (27), which fits whole 500 mL bags of synthetic colloid, with 250 mL as half a bag. There are also 60 values at 100–400 mL.\n- Liver transplant (92 of 108 cases): spread evenly over 100, 200, 300, 400, 500 and beyond in steps of 100, which fits counts of 100 mL vials of 20% albumin.\n- Kidney surgery (100): almost all 50 mL or less. Kidney transplants record 35 (55 of 64 non-zero) or 20; donor nephrectomies record 15–40.\n- 10 odd values (75, 120, 380, 401, 503, 650, 1,001, 1,020 ×2, 1,050) fit no pattern and are probably typed exactly.\n",
      "discussion": "- Values on a container grid are treated as exact counts (500 mL bags; 100 mL vials in liver transplant), with no rounding model.\n- Partial amounts in other surgery (100–400, 250) are treated as estimated to the nearest 100 mL (±50 mL), flagged as possibly albumin vials.\n- Odd values get native 1 mL resolution.\n- No colloid container explains the kidney-surgery values.\n",
      "limitations": "- The 100–400 mL values in other surgery could be partial bags or 100 mL albumin vials; the data cannot tell these apart.\n",
      "open_questions": [
        {
          "title": "What the kidney-surgery colloid values record",
          "kind": "phenomenon",
          "summary": "What do the kidney-surgery values record: a different product, a fixed small dose, or albumin in grams? Until confirmed, values of 50 mL or less are flagged `unit_uncertain` at native resolution. The VitalDB team or local protocols would settle it."
        }
      ],
      "tags": [
        "from the resolution work"
      ],
      "derived": {
        "examples": [
          "fluid-balance"
        ]
      }
    },
    "value-model-aneend": {
      "id": "value-model-aneend",
      "name": "How the anesthesia end time was charted",
      "summary": "The charted anesthesia end time is a mixture of 4 ways of writing a clock time (ceiling_30min, ceiling_10min, ceiling_5min, clock_minute), modelled as an offset from a device time.",
      "date": "2026-10-18",
      "status": "current",
      "methods": [
        "value-resolution",
        "clinical-time"
      ],
      "variables": [
        "aneend"
      ],
      "model": "vitaldb-resolution/vitaldb_resolution/data/models/aneend.model.json",
      "data": "- Anaesthesia ends from `clinical_data.csv`, modelled against the recording end.\n- Excluded: the anaesthesia ends derived from an imputed op end (stored with floating-point noise), the 1900 placeholder in [[case:4476]], and two values outside the model range, [[case:3980]] (+720 min, probably a 12-hour clock error) and [[case:4959]] (+1,441 min, probably a date error).\n- 2,037 anaesthesia ends (32%) fall after the end of recording. This is plausible, since monitors come off before handover, but these cannot be checked against the signals.\n",
      "code": [
        "vitaldb-resolution/vitaldb_resolution/engine.py"
      ],
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "figures": [
        {
          "id": "observed-vs-model",
          "file": "models/aneend.png",
          "theme": "heaping",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "The minute past the hour of every charted time: the heaps on multiples of 5, and at the standard first-case times, are the rounding modes the model fits.",
          "alt": "Bar chart of charted times by minute past the hour, with tall bars on multiples of five."
        }
      ],
      "results": "[[figure:observed-vs-model]]\n\n- Minutes :00, :10, :20, :30, :40 and :50 have 706–872 cases each, against 192–271 at the 5s. :00 and :30 are slightly higher (872 each).\n- Rounded ends are written up to the next mark: ceiling beats nearest by 168 BIC points and floor by 782. An end charted as 11:30 means \"finished by 11:30\".\n- Hour, emergency status and department made no difference.\n- On average the true end is about 4 min before the charted end, so anaesthesia durations computed from charted times run long by a few minutes.\n- Since vitaldb-resolution v0.6.0 (18 October; model version 2): the two out-of-range records are excluded\n  (not corrected), and the stored and recomputed log-likelihoods now agree: the whole gap came from those two\n  records. The parameters are unchanged.\n",
      "background": "- Anaesthesia end is a charted event time with a 1-minute chronon.\n- It is almost entirely rounded: 95% fall on a 5-minute clock mark, with clear 10-minute preference.\n",
      "method": "- Anaesthesia end = recording end + G, with G a log-spline density (K = 8).\n- Nearest, floor and ceiling anchors were compared by BIC.\n- Modes at 5, 10, 30 minutes and the clock minute were fitted. Hour of day, emergency status and department were tested as covariates.\n",
      "discussion": "- Ceiling rounding for anaesthesia end and floor rounding for op end together inflate charted emergence by about 6–7 min.\n- Correction: an earlier fit did not exclude the derived ends, because their noise (about 10⁻⁶ s) slipped under the test threshold. The model was refitted without them and with a wider offset range; the conclusions are unchanged.\n- Before v0.6.0 the package's log-likelihood differed from the stored one by +1,381.5: the two out-of-range records had been left in the fit at probability 10⁻³⁰⁰ (−690.8 each). They are constants, so the parameters were unaffected; model version 2 excludes them and the two now agree.\n",
      "limitations": "- Ends charted after the recording stops cannot be validated against the signals.\n",
      "open_questions": [
        {
          "title": "Two out-of-range end times as 12-hour or date errors",
          "kind": "hypothesis",
          "summary": "Two anaesthesia-end records lie at 720 and 1,441 minutes, outside the modelled range. A 12-hour clock slip and a one-day date error are the obvious explanations; neither is confirmed."
        }
      ],
      "derived": {
        "variables": [
          "aneend"
        ],
        "observations": [
          "emr-rounded-up"
        ],
        "examples": [
          "oliguria-labels",
          "regression-with-rounding",
          "urine-output-per-kg-per-hour"
        ],
        "code_urls": {
          "vitaldb-resolution/vitaldb_resolution/engine.py": "https://github.com/metrology-atlas/vitaldb-resolution/blob/main/vitaldb_resolution/engine.py"
        }
      }
    },
    "value-model-anestart": {
      "id": "value-model-anestart",
      "name": "How the anesthesia start time was charted",
      "summary": "The charted anesthesia start time is a mixture of 5 ways of writing a clock time (nominal_0740, nominal_0730, nearest_10min, nearest_5min, clock_minute), modelled as an offset from a device time.",
      "date": "2026-10-18",
      "status": "current",
      "methods": [
        "value-resolution",
        "clinical-time"
      ],
      "variables": [
        "anestart"
      ],
      "model": "vitaldb-resolution/vitaldb_resolution/data/models/anestart.model.json",
      "data": "- 6,387 anaesthesia starts from `clinical_data.csv`.\n- The clock minute is reconstructed from the admission date (see the clock-reconstruction study). Two cases with an admission that is not a whole minute, [[case:1342]] and [[case:5723]], are flagged `clock_uncertain`.\n- The charted start falls before recording start in 91.7% of cases (median 583 s).\n",
      "code": [
        "vitaldb-resolution/vitaldb_resolution/engine.py"
      ],
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "figures": [
        {
          "id": "observed-vs-model",
          "file": "models/anestart.png",
          "theme": "heaping",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "The minute past the hour of every charted time: the heaps on multiples of 5, and at the standard first-case times, are the rounding modes the model fits.",
          "alt": "Bar chart of charted times by minute past the hour, with tall bars on multiples of five."
        }
      ],
      "results": "[[figure:observed-vs-model]]\n\n- Only 52% of starts fall on a 5-minute clock mark. Marks ending in 0 are favoured over those ending in 5, and :30 and :40 dominate.\n- Of the 1,870 anaesthesia starts between 07:00 and 07:59, 420 are at exactly 07:40, which suggests a nominal first-case start time.\n- Rounded starts are written to the nearest mark: nearest beats ceiling by 47 BIC points and floor by 289.\n- The first-case clock-time density improves the model by 722 BIC points; almost no first case starts before 07:30.\n- Evenings and nights round much more; emergencies round a little more to 5 min.\n- The package reproduces the stored log-likelihood within 0.11 (JAX fitter vs engine grid).\n- Anaesthesia start by department: 52–56% of charted minutes fall on a multiple of 5 (20% if unrounded) and\n  26–34% on a multiple of 10, with no clear difference between departments. Emergency cases are rounded slightly\n  more often (58.7% on a multiple of 5, against 51.2% for elective).\n",
      "background": "- Anaesthesia start is an event time copied from the EMR into the case file. Its chronon is 1 minute.\n- The dataset paper (Lee et al., Sci Data 2022) says anaesthesia start and end are recorded at 5-minute intervals. That holds for anaesthesia end, but not for most anaesthesia starts.\n- Of the gaps from anaesthesia start to op start, 48.4% are multiples of 5 min, against 20% by chance.\n",
      "method": "- Each charted time is a rounded version of a true time that sits a variable offset G before the device recording start, which is timed to the second.\n- G has a log-spline density (K = 12). Tying the latent to the recording gives each case its own prior and makes the anchor of each mode identifiable.\n- First cases (recording starting 06:30–09:00) also get a clock-time density for the true start, plus two nominal values (07:40 and 07:30).\n- Nearest, floor and ceiling anchors were compared by BIC.\n- Propensities were allowed to depend on first-case status, evening or night, and emergency status.\n",
      "discussion": "- A start recorded off the 5-minute grid is right to the minute.\n- A rounded start is uncertain by ±2.5 or ±5 min, centred on the recorded time.\n- A 07:40 or 07:30 start written for a first case may be off by 10 min or more; the recording start then carries most of the information.\n- Because both starts use the nearest mark, induction to incision is nearly unbiased (−0.5 min).\n- The API needs the case's recording start and admission date to place the value on the clock, plus emergency status.\n",
      "limitations": "- The capture windows of the two nominal first-case values are not identifiable.\n- The charted start sits a median of about 10 minutes before monitoring begins, and device-marked induction a\n  median 14 minutes after it (see the open question below): it marks the start of anaesthesia care, not induction.\n",
      "open_questions": [
        {
          "title": "Anaesthesia start ignores the 5-minute rule",
          "kind": "phenomenon",
          "summary": "Why does anaesthesia start not follow the 5-minute rule the paper describes? Were there two entry methods, or a change of system partway through?"
        },
        {
          "title": "What event anaesthesia start marks",
          "kind": "phenomenon",
          "summary": "Partly answered: device-marked induction (BIS loss of consciousness, else the propofol pump start) comes a median 14 minutes after the charted start (10th–90th percentile 6 to 25 minutes) in 2,913 cases ([[example:induction-hypotension]]), so the charted time marks the start of anaesthesia care, not induction. Which event of care it records (arrival, monitoring, pre-oxygenation) is open."
        },
        {
          "title": "No time axis for era effects",
          "kind": "assumption",
          "summary": "Resolution cannot be checked by era: case IDs are random (the release says so, and they are uncorrelated with anything dated), and dates are removed. The models assume rounding habits were constant over the years the data cover. Department makes no difference; emergency cases are rounded a little more often."
        }
      ],
      "derived": {
        "variables": [
          "anestart"
        ],
        "observations": [
          "emr-rounded-up"
        ],
        "examples": [
          "oliguria-labels",
          "regression-with-rounding",
          "urine-output-per-kg-per-hour"
        ],
        "code_urls": {
          "vitaldb-resolution/vitaldb_resolution/engine.py": "https://github.com/metrology-atlas/vitaldb-resolution/blob/main/vitaldb_resolution/engine.py"
        }
      }
    },
    "value-model-bmi": {
      "id": "value-model-bmi",
      "name": "Body mass index, derived from its inputs",
      "summary": "Body mass index computed from the posteriors of height, weight.",
      "date": "2026-10-18",
      "status": "current",
      "methods": [
        "value-resolution"
      ],
      "variables": [
        "bmi"
      ],
      "model": "vitaldb-resolution/vitaldb_resolution/data/models/bmi.model.json",
      "code": [
        "vitaldb-resolution/vitaldb_resolution/engine.py"
      ],
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "background": "- BMI is computed, not entered. Every one of the 6,388 recorded values equals weight ÷ (height/100)² rounded to 0.1.\n- So it has no rounding behaviour of its own, and the recorded value says nothing about true height and weight beyond those two fields.\n",
      "data": "- All 6,388 recorded BMI values, recomputed from height and weight as a check.\n- Inputs come from `height.model.json` and `weight.model.json`.\n",
      "method": "- No rounding model. BMI is a derived variable (`kind: derived`, evaluated by `derived_samples`).\n- For each case, height and weight are drawn from their own posteriors (sex, age, emergency status, department), optionally with measurement variability, and BMI is computed per draw.\n- The recorded BMI is kept only as a consistency check.\n",
      "results": "- With measured inputs, BMI is known to about ±0.02 from the recording alone; measurement variability widens that to about ±0.4.\n- A possibly written-in 160.0 cm height can come from the nearest-10 cm mode, so BMI spreads from 22.5 to 24.8 (95%).\n- With variability, the self-report bias for older women (heights over-reported by about 1.3 cm at 60–69) moves the centre up to about 23.9 and widens the interval to 22.0–26.3.\n- Over the whole release (6,388 records, the library's default call: rounding only, no measurement variability), the median 95% width of the true BMI is 0.054 kg/m² and the 90th centile 0.31. The widths fall into three groups set by how height and weight were written: both with decimals (4,562 records, median 0.050), height or weight in whole cm or kg (1,487, median 0.27), and heights on a 5 cm mark (339, median 1.8). The widest 210 records all have such a height, consistent with the model reading them as possibly written in.\n",
      "discussion": "- The API returns BMI as a distribution built from the height and weight distributions for the same case, not from the recorded BMI.\n- The same approach is planned for eGFR, ICU days and length of stay.\n",
      "limitations": "- Height and weight are drawn independently given sex and age, so their correlation is ignored. This matters only when an input posterior is wide.\n",
      "figures": [
        {
          "id": "bmi-spread",
          "file": "bmi-spread.png",
          "theme": "distributions of measured parameters",
          "source": "vitaldb-bench tools/bmi_spread_figure.py",
          "caption": "How wide the true BMI is, record by record, from the rounding of height and weight alone (no measurement variability). Left: the 95% interval width over all 6,388 records, coloured by how the inputs were written. Right: the same widths against the recorded BMI. The three bands are the three ways of writing the inputs; within each, the width grows with BMI because it scales with weight over height squared.",
          "alt": "Left, a stacked histogram of interval widths on a log scale with three separate humps. Right, a scatter of width against recorded BMI showing three horizontal bands that rise slowly with BMI."
        }
      ],
      "derived": {
        "variables": [
          "bmi"
        ],
        "code_urls": {
          "vitaldb-resolution/vitaldb_resolution/engine.py": "https://github.com/metrology-atlas/vitaldb-resolution/blob/main/vitaldb_resolution/engine.py"
        }
      }
    },
    "value-model-height": {
      "id": "value-model-height",
      "name": "Rounding and heaping of height",
      "summary": "Recorded height is a mixture of 5 rounding modes (tens, whole_cm, half_cm, round_up_from_x9, measured_0.1); the model gives the true value's distribution for each record.",
      "date": "2026-10-13",
      "status": "current",
      "methods": [
        "value-resolution"
      ],
      "variables": [
        "height"
      ],
      "model": "vitaldb-resolution/vitaldb_resolution/data/models/height.model.json",
      "data": "- 6,388 values, none missing or zero. One value is off the 0.1 cm grid (157.36).\n- 57 cases are children (42 patients), down to 42 cm. They get native resolution only (flag `child_native_resolution`).\n- Repeat cases copy the height: of 237 patients with more than one case, 158 have the same height every time.\n- The fit uses adults, first case per patient (6,041). Later cases with the same height get the same posterior.\n- Within each sex, height is close to normal (women 156.5 ± 6.1 cm, men 168.3 ± 6.4 cm).\n",
      "code": [
        "vitaldb-resolution/vitaldb_resolution/engine.py"
      ],
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "figures": [
        {
          "id": "observed-vs-model",
          "file": "models/height.png",
          "theme": "heaping",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Every recorded value (blue lines) against what the fitted model predicts (orange dots): its rounding rules applied to its true-value density. Heaps on round numbers are the rounding modes.",
          "alt": "Spike histogram of recorded values with model predictions overlaid; spikes on round numbers."
        }
      ],
      "results": "[[figure:observed-vs-model]]\n\n- After removing the 10% expected by chance, about 11% of elective and 21% of emergency heights were written as whole centimetres. Thoracic surgery is also higher (26% against 20% overall).\n- The .9 digit is depleted (7.3% of elective heights, against about 9% for .1 to .4): a height just under a whole centimetre is often written as the centimetre above.\n- There is a small excess at .5 (10.3%).\n- Among whole-centimetre heights, 150, 160 and 170 are about 1.5 times their neighbours, but 155 and 165 are not.\n- Each added component improved BIC: from 64,888 with no emergency effect to 64,852.5 with all of them.\n- Emergency status on the 10 cm mode, department, and a heavy-tailed residual did not help.\n- The engine reproduces the log-likelihood exactly (−32,378.3).\n",
      "references": [
        "leatt1986spinal",
        "voss1997diurnal",
        "healey2011diurnal"
      ],
      "background": "- Height is stored to 0.1 cm, so it looks measured. But some heights were written as whole centimetres, probably when the patient was not measured.\n- It is the first variable whose resolution is mixed at the finest digit.\n- Height changes by a centimetre or two over the day, which is handled separately as measurement variability.\n",
      "method": "- Screened the last digit by emergency status, department, age, sex and calendar position.\n- Latent: normal regression on sex and age (quadratic), with SD by sex.\n- Candidate modes: whole cm, round tens, a half-centimetre mode, and a one-sided round-up from .9 with a ceiling anchor.\n- Tested a rounding propensity that depends on emergency status, a first for the engine.\n- Compared by BIC: no emergency effect, emergency on whole cm, plus the half-cm mode, plus round-up, and both.\n- A 1 cm resolution override was fitted and then dropped (see discussion).\n",
      "discussion": "- The 1 cm override worked as a fit, but it threw away the decimal digit, which is the best clue to whether a height was measured. The emergency effect vanished and the 160 heap could no longer be told apart from measured heights near 160.\n- So the rounding model answers \"what was written down?\", and a separate measurement-variability field answers \"how far is that from the patient's true height today?\".\n- The posterior weight of the non-measured modes is reported as `possibly_written_in`. It is zero for any height whose last digit is not 0 or 5.\n- Measured heights get SD 0.5 cm (diurnal loss of about 14–17 mm plus stadiometer reproducibility; Leatt, Reilly & Troup 1986; Voss & Bailey 1997).\n- Written-in heights are treated as self-reported, with bias by sex and age band from the 2018 Korea Community Health Survey (171,384 adults) and an SD of about 2.6 cm.\n- The API needs sex, age and emergency status.\n",
      "limitations": "- Measurement variability comes from the literature, not from this dataset, and is marked provisional.\n- Children are not modelled.\n",
      "derived": {
        "variables": [
          "height"
        ],
        "code_urls": {
          "vitaldb-resolution/vitaldb_resolution/engine.py": "https://github.com/metrology-atlas/vitaldb-resolution/blob/main/vitaldb_resolution/engine.py"
        }
      }
    },
    "value-model-intraop-crystalloid": {
      "id": "value-model-intraop-crystalloid",
      "name": "Rounding and heaping of intraoperative crystalloid",
      "summary": "Recorded intraoperative crystalloid is a mixture of 3 rounding modes (100 mL, 50 mL, 10 mL (base)); the model gives the true value's distribution for each record.",
      "date": "2026-10-18",
      "status": "current",
      "methods": [
        "value-resolution"
      ],
      "variables": [
        "intraop-crystalloid"
      ],
      "model": "vitaldb-resolution/vitaldb_resolution/data/models/intraop_crystalloid.model.json",
      "data": "- 5,980 values; 408 missing (short cases, median 1.7 h against 3.1 h); no zeros; maximum 23,800 mL.\n- Only 6 values are off the 10 mL grid, so the resolution is 10 mL.\n- The fit uses 5,974 values on the 10 mL grid.\n",
      "code": [
        "vitaldb-resolution/vitaldb_resolution/engine.py"
      ],
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "figures": [
        {
          "id": "observed-vs-model",
          "file": "models/intraop-crystalloid.png",
          "theme": "heaping",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Every recorded value (blue lines) against what the fitted model predicts (orange dots): its rounding rules applied to its true-value density. Heaps on round numbers are the rounding modes.",
          "alt": "Spike histogram of recorded values with model predictions overlaid; spikes on round numbers."
        }
      ],
      "results": "[[figure:observed-vs-model]]\n\n- Values are written to the nearest 100 mL about half the time and to the nearest 50 mL the other half, the same at every volume.\n- Volume-dependent propensities fit no better than constant ones (BIC 47,384 against 47,374).\n- Bag-size modes do not help (BIC 47,416). There is no special heaping at bag sizes.\n- A recorded multiple of 100 mL is uncertain by about ±50 mL; any other multiple of 50 by about ±25 mL.\n- Since vitaldb-resolution v0.6.0 (18 October; model version 2): anaesthesia duration is a covariate of the\n  latent volume (BIC better by 6,254). Department on the rounding propensities made the fit worse (BIC +22) and\n  is not used. Rounding to the nearest anchor beats floor rounding by BIC.\n",
      "background": "- Crystalloid volume (`intraop_crystalloid`) is a hand-entered intraoperative total.\n- The first screen found 98% of values on multiples of 50 mL, which suggested bag sizes plus partial bags.\n- In the end it is the simplest value variable so far.\n",
      "method": "- Log-spline latent with K = 6.\n- Constant 100 and 50 mL propensities were compared with volume-dependent ones.\n- 500 and 1,000 mL bag-size modes were added and tested.\n",
      "discussion": "- Crystalloid volume is very strongly tied to case duration (Spearman 0.82).\n- Conditioning the latent on duration narrows the posteriors considerably; model version 2 does so (see results).\n",
      "derived": {
        "variables": [
          "intraop-crystalloid"
        ],
        "examples": [
          "fluid-balance"
        ],
        "code_urls": {
          "vitaldb-resolution/vitaldb_resolution/engine.py": "https://github.com/metrology-atlas/vitaldb-resolution/blob/main/vitaldb_resolution/engine.py"
        }
      }
    },
    "value-model-intraop-ebl": {
      "id": "value-model-intraop-ebl",
      "name": "Rounding and heaping of estimated blood loss",
      "summary": "Recorded estimated blood loss is a mixture of 5 rounding modes (preferred small values, 500 mL, 100 mL, 50 mL, 10 mL (base)); the model gives the true value's distribution for each record.",
      "date": "2026-10-18",
      "status": "current",
      "methods": [
        "value-resolution"
      ],
      "variables": [
        "intraop-ebl"
      ],
      "model": "vitaldb-resolution/vitaldb_resolution/data/models/intraop_ebl.model.json",
      "data": "- 3,987 non-missing values (2,401 missing, 38%) and 11 zeros. The maximum is 30,100 mL, and 214 values are above 1,000 mL.\n- 17 positive values are not multiples of 5 (e.g. 2, 12, 472 mL). Only 26 (0.7%) are odd multiples of 5, so the resolution is overridden to 10 mL.\n- Cases with a recorded blood loss are longer (median 3.6 h, against 2.0 h when missing). Blood loss and urine output are both missing in 1,659 cases.\n- The fit uses 3,933 positive values on the 10 mL grid.\n",
      "code": [
        "vitaldb-resolution/vitaldb_resolution/engine.py"
      ],
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "figures": [
        {
          "id": "observed-vs-model",
          "file": "models/intraop-ebl.png",
          "theme": "heaping",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Every recorded value (blue lines) against what the fitted model predicts (orange dots): its rounding rules applied to its true-value density. Heaps on round numbers are the rounding modes.",
          "alt": "Spike histogram of recorded values with model predictions overlaid; spikes on round numbers."
        }
      ],
      "results": "[[figure:observed-vs-model]]\n\n- The grain scales with magnitude. Below 50 mL, 95% of values are multiples of 10. From 100 mL the dominant step is 100 mL, and above 500 mL the 500 and 1,000 mL anchors appear.\n- 50 mL holds 795 records, 20% of all values, and the 50–99 mL band is 91% exactly 50. Meanwhile 40 mL has only 14 records against 155 at 30 mL.\n- Neighbours of anchors are almost empty: the multiples of 10 from 60 to 90 mL total 75 records against 627 at 100 mL.\n- Absolute modes beat relative modes decisively (AIC lower by more than 2,500). Relative modes cannot produce the 50 mL steps that dominate 100–999 mL.\n- Below 75 mL, about 40% of estimates go to the nearest of 10, 20, 30 or 50 mL, skipping 40. This captures both the 50 mL heap and the missing 40s.\n- Representative posteriors: a recorded 30 mL has 95% interval 25.5–39.5 mL; 50 mL, 26.5–73.5; 300 mL, 254.5–344.5; 1,000 mL, 779.5–1,188.5.\n- Since vitaldb-resolution v0.6.0 (18 October; model version 2): anaesthesia duration is a covariate of the\n  latent volume (BIC better by 1,751). Department on the rounding propensities made the fit worse (BIC +51) and\n  is not used. Rounding to the nearest anchor beats floor rounding by BIC.\n",
      "background": "- Estimated blood loss (`intraop_ebl`) is a hand-entered EMR total.\n- It behaves differently from urine output in two ways: the rounding grain grows with the size of the estimate, and 50 mL looks like a default for \"minimal\" loss.\n- Blood loss is itself an estimate. Nothing is measured to the millilitre, so the \"true\" value is what a careful estimator would have said, and rounding absorbs estimation coarseness as well as charting preference.\n",
      "method": "- Heaping was screened by magnitude: the share of values whose coarsest anchor is 10, 50, 100, 500 or 1,000 mL, in seven recorded ranges.\n- All candidate models use a log-spline latent with K = 6.\n- Relative modes (leading digit, half, tenth) were compared with absolute modes (500, 100, 50 mL on a 10 mL base), both with propensities that vary with log volume.\n- A preferred-value set {10, 20, 30, 50} below 75 mL was added and tested.\n- A separate nominal 50 mL component below a threshold T was tested on top (best T = 200 mL).\n- The model was refitted without the 30,100 mL value to check its influence.\n",
      "discussion": "- Relative precision of this kind is typical of a visual estimate, so every mode's propensity has to depend on magnitude, not just one.\n- A nominal \"minimal loss\" 50 mL component adds little once the preferred-value set is in (BIC within 4). The working model omits it.\n- The 30,100 mL value does not distort the fit. Without it, the latent median (137 mL) and all propensities are unchanged to two decimal places; only the latent 99th percentile moves (3,636 to 3,549 mL).\n- The preferred-value set introduced a new mode type (\"set\") to the engine.\n",
      "limitations": "- Some misfit remains, mostly too few 70–80 mL and too many 90 mL predictions.\n- Capture windows are much wider than for urine output, because anchor neighbours are nearly empty.\n- The 30,100 mL value is kept and flagged.\n",
      "derived": {
        "variables": [
          "intraop-ebl"
        ],
        "examples": [
          "fluid-balance"
        ],
        "code_urls": {
          "vitaldb-resolution/vitaldb_resolution/engine.py": "https://github.com/metrology-atlas/vitaldb-resolution/blob/main/vitaldb_resolution/engine.py"
        }
      }
    },
    "value-model-intraop-ppf": {
      "id": "value-model-intraop-ppf",
      "name": "Rounding and heaping of propofol bolus",
      "summary": "Recorded propofol bolus is a mixture of 3 rounding modes (preferred values, 50 mg, 10 mg (base)); the model gives the true value's distribution for each record.",
      "date": "2026-10-13",
      "status": "current",
      "methods": [
        "value-resolution"
      ],
      "variables": [
        "intraop-ppf"
      ],
      "model": "vitaldb-resolution/vitaldb_resolution/data/models/intraop_ppf.model.json",
      "data": "- 2,335 non-zero values; 4 off the 10 mg grid (flagged) and 3 without age or weight, leaving 2,328 to fit.\n- Resolution 10 mg, no override.\n- The 3,719 zeros among 6,043 general anaesthetics are not fitted. Many of these probably had propofol by infusion (TIVA), recorded on the pump tracks.\n",
      "code": [
        "vitaldb-resolution/vitaldb_resolution/engine.py"
      ],
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "figures": [
        {
          "id": "observed-vs-model",
          "file": "models/intraop-ppf.png",
          "theme": "heaping",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Every recorded value (blue lines) against what the fitted model predicts (orange dots): its rounding rules applied to its true-value density. Heaps on round numbers are the rounding modes.",
          "alt": "Spike histogram of recorded values with model predictions overlaid; spikes on round numbers."
        }
      ],
      "results": "[[figure:observed-vs-model]]\n\n- The dose rises with weight^0.6 rather than in proportion, falls about 4% per decade of age, and is about 10% higher in men of the same weight.\n- The residual is heavy-tailed (df 3.3). A log-normal residual is about 330 BIC points worse.\n- A weight-dependent propensity adds nothing (ΔBIC 1.2), because the latent already moves the heap from 100 to 150 mg.\n- Closing the set ends is worse (−log L 3,867–3,929 against 3,850).\n- The engine reproduces the likelihood (−3,847.5 against −3,850.2 in closed form).\n",
      "background": "- Bolus drug totals are mostly sums of whole standard doses, so most are exact counts, not rounded estimates.\n- Propofol bolus (`intraop_ppf`) is the exception: it is a single weight-based induction dose, written as a preferred value.\n- The dose tracks weight (Spearman 0.54; median 1.8 mg/kg), but recorded values pile onto 100, 120 and 150 mg. 44% of patients weighing 50–60 kg got exactly 100 mg, while 57% of those over 80 kg got exactly 150 mg.\n- It is the first model whose latent depends on the patient.\n",
      "method": "- Latent: log intended dose as a regression on weight, age and sex, with a Student-t residual (new family `log_student_t_regression`).\n- Rounding: a preferred-value set, nearest 50 mg and nearest 10 mg.\n- Set members were chosen by BIC, adding and removing candidates from 50 to 200 mg. Open set ends were compared with closed ends.\n- A log-normal residual was compared with the Student-t, and a propensity that rises with weight was tested.\n- The engine's likelihood on its 0.25 mg grid was checked against the closed form.\n",
      "discussion": "- Doses below 60 mg or above 150 mg that are rounded to the set are written as 60 or 150, because the set ends are open.\n- Zeros return status `zero_or_not_recorded` with no distribution, since a 0 can mean none given or given by another route.\n- The API needs weight, age and sex for this variable.\n",
      "limitations": "- The main misfits are 90 mg (144 recorded, 267 expected) and 130 mg (64 against 29).\n- A recorded 150 mg in a light patient gets a long upper tail (95% up to 290 mg at 50 kg).\n",
      "open_questions": [
        {
          "title": "Is 150 mg a ceiling?",
          "kind": "phenomenon",
          "summary": "Is 150 mg a ceiling people write for any large dose, or are larger doses usually given as a second bolus and summed? The upper tail is right only in the first case. Adding 200 mg to the set did not pass BIC."
        },
        {
          "title": "Misfits at 90 mg and 130 mg",
          "kind": "phenomenon",
          "summary": "The misfits at 90 mg and 130 mg are unexplained."
        }
      ],
      "derived": {
        "variables": [
          "intraop-ppf"
        ],
        "examples": [
          "propofol-against-pump"
        ],
        "code_urls": {
          "vitaldb-resolution/vitaldb_resolution/engine.py": "https://github.com/metrology-atlas/vitaldb-resolution/blob/main/vitaldb_resolution/engine.py"
        }
      }
    },
    "value-model-intraop-uo": {
      "id": "value-model-intraop-uo",
      "name": "Rounding and heaping of intraoperative urine output",
      "summary": "Recorded intraoperative urine output is a mixture of 4 rounding modes (100 mL, 50 mL, 10 mL, 5 mL (base)); the model gives the true value's distribution for each record.",
      "date": "2026-10-18",
      "status": "current",
      "methods": [
        "value-resolution"
      ],
      "variables": [
        "intraop-uo"
      ],
      "model": "vitaldb-resolution/vitaldb_resolution/data/models/intraop_uo.model.json",
      "data": "- 3,707 non-missing values; 2,681 cases are blank.\n- 42% of cases have no urine output, probably because no catheter was placed. Cases without it are much shorter (median 1.8 h), so the latent distribution is conditional on catheterisation.\n- 15 values (0.4%) are not multiples of 5 mL. They are flagged `off_grid` and the resolution is overridden to 5 mL.\n- The 23 exact zeros were set aside in the original fit; since v0.6.0 they have their own rule (see Results).\n- The fit uses the 3,669 positive values on the 5 mL grid.\n",
      "code": [
        "vitaldb-resolution/vitaldb_resolution/engine.py"
      ],
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "figures": [
        {
          "id": "observed-vs-model",
          "file": "models/intraop-uo.png",
          "theme": "heaping",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Every recorded value (blue lines) against what the fitted model predicts (orange dots): its rounding rules applied to its true-value density. Heaps on round numbers are the rounding modes.",
          "alt": "Spike histogram of recorded values with model predictions overlaid; spikes on round numbers."
        }
      ],
      "results": "[[figure:observed-vs-model]]\n\n- On the first screen, 22.6% of values sit on 100 mL and 20.5% on 50 mL (not 100), against 1% each expected with no rounding. Only 0.4% are at 1 mL resolution.\n- Values 10 mL from a 50 mL anchor are depleted: 677 records next to the anchors against 888 in the middle of each block. A smooth latent cannot do this. It matches Rietveld's distance-decay effect.\n- Distance decay is real (likelihood-ratio statistic 22.4 on 1 df in step 2). A near-step decay shape almost matches the near/far counts (667 / 901 predicted against 677 / 888 observed).\n- The log-spline beats the log-normal by 14.6 log-likelihood units for 2 extra parameters. A gamma latent fits clearly worse than the log-normal.\n- A 25 mL mode is not needed: its fitted propensity goes to zero.\n- A recorded 50, 100 or 500 mL is uncertain by roughly ±20–40 mL. A recorded 160 mL is uncertain by only ±5 mL, because 160 can only come from the 5 and 10 mL modes.\n- A recorded 100 mL has only a 22% chance of having been read to 10 mL or finer.\n- Grid of positive urine records by department (100 mL / 50 mL / 10 mL / 5 mL): gynaecology 40% / 33% / 27% /\n  0%, urology 29% / 49% / 22% / 0%, general 23% / 21% / 47% / 8%, thoracic 14% / 14% / 59% / 13% (chi-squared p\n  < 10⁻³⁰). Among values of 100 mL or more, the share on the 100 mL grid is 55% in gynaecology, 43% in urology,\n  30% in general and 21% in thoracic surgery. Emergency and elective cases do not differ (p = 0.77).\n- Since vitaldb-resolution v0.6.0 (18 October; model version 2): anaesthesia duration is a covariate of the\n  latent volume (BIC better by 926), and the rounding propensities depend on department (better by 87). Rounding\n  to the nearest anchor beats floor rounding (H1) by BIC, so the anchor is now tested. A recorded 0 has its own\n  rule: 9 zeros are flagged `zero_anuric` (creatinine above 2 mg/dL or ASA 6) and 14 `zero_unexplained`; for a\n  recorded 0 the latent still carries about 54% of the posterior weight, so only `zero_anuric` stands for\n  confirmed anuria.\n",
      "background": "- Intraoperative urine output (`intraop_uo`) is a hand-entered EMR total, probably charted on the anaesthesia record.\n- It was the first value variable worked through, and the per-variable checklist used for every later variable was written from it.\n- The finest observed resolution is 1 mL, but almost every value is rounded to at least 5 mL, and two in five to 50 or 100 mL.\n- A urine volume can be rounded at three points before it is charted: at the device graduation (H1), when readings are summed (H2), or when the final total is charted (H3).\n",
      "method": "- First screen: each value was placed in one of five nested groups by the coarsest anchor it sits on (100, 50, 10, 5 or 1 mL), and the shares were compared with a flat, no-rounding expectation.\n- Step 1, binned exploration: 100 mL bins centred on the 100 mL anchors, a flat latent within each bin, and propensities by back-substitution as in the manuscript's closed form.\n- Step 2, parametric latent: a log-normal latent fitted jointly with the propensities by maximum likelihood. Plain nearest rounding (model A) was compared with distance decay for the 50 mL mode (model B).\n- Step 3, log-spline latent: log f as a cubic B-spline in log volume, with K chosen by BIC. Tested a steeper decay shape, volume-dependent 50 mL rounding and a 25 mL mode.\n- Step 4, robustness: 100 simulated datasets refitted from a generic start, a profiled hard-threshold capture window, a 1 mL-base sensitivity fit and a more flexible latent (K = 8).\n- The final model is the threshold form: rounding to 50 mL only for true values within 20 mL of the anchor, at a rate that rises with volume.\n",
      "discussion": "- Rounding looks similar across volumes and across elective and emergency cases. This argues against strong device-graduation effects (H1).\n- Summing readings (H2) can explain multiples of 10, but not the extra 50 and 100 mL peaks. Those point to a charting preference (H3).\n- The steep decay reads naturally as behaviour: a reading of 40 or 60 mL is rounded to 50 almost as often as one right on it, while readings 20–25 mL away are nearly as close to another 10 mL value.\n- The threshold form was chosen over the smooth steep decay because it is simpler to explain and better identified. The best step, at 20 mL, is within 1.4 log-likelihood units of the smooth decay.\n- The latent slope tilts each posterior. At 50 mL the latent is still rising, so the mean is pulled up to 50.9. At 500 mL it is falling, so the mean drops to 498.9.\n- The 50 mL component spans ±20 mL, not ±25: the interval is the capture window, not the full half-resolution.\n",
      "limitations": "- The decay steepness κ is not identifiable: in about half the simulation refits it ran off towards infinity. The data say \"near-step\" but cannot tell κ = 5 from a true step.\n- The volume slope for 50 mL rounding is poorly determined in simulation (median 0.19, 95% interval 0.07–1.65).\n- The nonparametric EM check was deferred, since K = 8 gives the same propensities. It would be run only if a reviewer asks.\n- Correction, 13 Oct: the fitting code let the 100 mL mode round small true volumes to 0, which zero-truncation then silently dropped. After the fix the posterior weights moved by 0.01 or less. `intraop_uo.model.json` holds the corrected fit.\n- Within each component the density is not flat. Until v0.6.0 the components were treated as uniform; the density within an interval is now computed on demand from the latent.\n",
      "open_questions": [
        {
          "title": "Thoracic records are much finer than gynaecology's",
          "kind": "phenomenon",
          "summary": "Thoracic surgery charts urine output mostly to 10 or 5 mL, gynaecology and urology mostly to 50 or 100 mL, even at the same volumes. The model now lets the rounding propensities depend on department (it improves BIC by 87), but why they differ is not known: a urometer with fine graduations in some theatres and a drainage bag in others would explain it. The urology split rests on 41 records."
        }
      ],
      "derived": {
        "variables": [
          "intraop-uo"
        ],
        "examples": [
          "fluid-balance",
          "oliguria-labels",
          "regression-with-rounding",
          "urine-output-per-kg-per-hour"
        ],
        "code_urls": {
          "vitaldb-resolution/vitaldb_resolution/engine.py": "https://github.com/metrology-atlas/vitaldb-resolution/blob/main/vitaldb_resolution/engine.py"
        }
      }
    },
    "value-model-opend": {
      "id": "value-model-opend",
      "name": "How the operation end time was charted",
      "summary": "The charted operation end time is a mixture of 4 ways of writing a clock time (floor_30min, floor_10min, floor_5min, clock_minute), modelled as an offset from a device time.",
      "date": "2026-10-18",
      "status": "current",
      "methods": [
        "value-resolution",
        "clinical-time"
      ],
      "variables": [
        "opend"
      ],
      "model": "vitaldb-resolution/vitaldb_resolution/data/models/opend.model.json",
      "data": "- 5,850 op ends, after excluding the 537 imputed from the recording (598–604 s before it ends; status `imputed`).\n- 92 op ends do not share the sub-minute offset of the other charted times in their case and are flagged `clock_offset_mismatch`.\n- 52 op ends come after anaesthesia end (`order_violation`) and 108 equal it (`same_time`).\n",
      "code": [
        "vitaldb-resolution/vitaldb_resolution/engine.py"
      ],
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "figures": [
        {
          "id": "observed-vs-model",
          "file": "models/opend.png",
          "theme": "heaping",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "The minute past the hour of every charted time: the heaps on multiples of 5, and at the standard first-case times, are the rounding modes the model fits.",
          "alt": "Bar chart of charted times by minute past the hour, with tall bars on multiples of five."
        }
      ],
      "results": "[[figure:observed-vs-model]]\n\n- The anchor is floor (nearest is 173 BIC points worse, ceiling 312): op ends are written as the 5-minute mark at or before the true end.\n- This is the opposite of anaesthesia end, which is written up to the next mark.\n- The true op end is on average about 2.5 min after the charted one, so charted op durations run about 2.5 min short.\n- The package reproduces the stored log-likelihood within 0.14.\n",
      "background": "- Op end is a charted event time with a 1-minute chronon.\n- About one in twelve op ends appears to have been set from the recording itself rather than from the record (see the imputed-times study).\n",
      "method": "- The true op end is modelled relative to the recording end with a log-spline offset (K = 12, chosen by BIC).\n- Nearest, floor and ceiling anchors were compared, along with 10- and 30-minute modes.\n- Posteriors were compared across K, because the latent is narrow relative to the grid.\n",
      "discussion": "- Related times are rounded in opposite directions, so the anchor is fitted per variable and never assumed shared.\n- Intervals inherit both biases: charted emergence (op end to anaesthesia end) is inflated by about 6–7 min.\n",
      "limitations": "- The latent has two narrow peaks (13 and 17 min before the recording ends). With most values on a 5-minute grid, fine structure in a narrow latent is hard to separate from rounding, so these peaks should not be read as real.\n",
      "open_questions": [
        {
          "title": "Was op end imputed deliberately?",
          "kind": "next step",
          "summary": "Was op end imputed from recording end deliberately? This is to be asked of the VitalDB team."
        },
        {
          "title": "Where the imputed op ends are",
          "kind": "next step",
          "summary": "Mapped on 18 October: thoracic surgery and cases under sedation or spinal anaesthesia have far more imputed op ends than the rest ([[observation:imputed-op-ends]]). The model already excludes them; analyses that use op end should check whether their subgroup is one of these."
        }
      ],
      "derived": {
        "variables": [
          "opend"
        ],
        "observations": [
          "imputed-op-ends"
        ],
        "code_urls": {
          "vitaldb-resolution/vitaldb_resolution/engine.py": "https://github.com/metrology-atlas/vitaldb-resolution/blob/main/vitaldb_resolution/engine.py"
        }
      }
    },
    "value-model-opstart": {
      "id": "value-model-opstart",
      "name": "How the operation start time was charted",
      "summary": "The charted operation start time is a mixture of 3 ways of writing a clock time (nearest_10min, nearest_5min, clock_minute), modelled as an offset from a device time.",
      "date": "2026-10-18",
      "status": "current",
      "methods": [
        "value-resolution",
        "clinical-time"
      ],
      "variables": [
        "opstart"
      ],
      "model": "vitaldb-resolution/vitaldb_resolution/data/models/opstart.model.json",
      "data": "- 6,387 op starts.\n- One op start does not share the sub-minute offset of the other charted times in its case and is flagged `clock_offset_mismatch`.\n",
      "code": [
        "vitaldb-resolution/vitaldb_resolution/engine.py"
      ],
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "figures": [
        {
          "id": "observed-vs-model",
          "file": "models/opstart.png",
          "theme": "heaping",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "The minute past the hour of every charted time: the heaps on multiples of 5, and at the standard first-case times, are the rounding modes the model fits.",
          "alt": "Bar chart of charted times by minute past the hour, with tall bars on multiples of five."
        }
      ],
      "results": "[[figure:observed-vs-model]]\n\n- There is no 30- or 60-minute rounding, and no effect of time of day or emergency status.\n- The anchor is nearest (floor is 14 BIC points worse, ceiling 101).\n- First-case incisions cluster at 08:20–08:50; the clock-time prior is 1,120 BIC points better.\n- Unlike anaesthesia start, there is no scheduled or nominal op start time.\n- Op start is well centred: a rounded value is uncertain by ±2.5 min.\n- Since vitaldb-resolution v0.6.0 (18 October; model version 2): the log-likelihood gap is explained. The old\n  fitter worked out the time-of-day prior on its own 0.1-minute grid; refitted with the library's likelihood the\n  model gains 33.2 in log-likelihood and the posteriors move by less than 0.03 minutes.\n",
      "background": "- Op start is a charted event time with a 1-minute chronon.\n- Op start, op end and anaesthesia end share a 5-minute grid. Op start falls on a 5-minute clock mark in 92% of cases.\n",
      "method": "- The same approach as the anaesthesia times: the true op start is placed a log-spline offset after the recording start (K = 10).\n- First cases get a clock-time prior, as for anaesthesia start.\n- Nearest, floor and ceiling anchors were compared. 30- and 60-minute modes and effects of time of day and emergency status were tested.\n",
      "discussion": "- Before v0.6.0 the package's log-likelihood differed from the stored one by −10.6. The cause was the fitter integrating the first-case clock prior on its own 0.1-min grid; model version 2 is fitted with the library's likelihood.\n",
      "derived": {
        "variables": [
          "opstart"
        ],
        "code_urls": {
          "vitaldb-resolution/vitaldb_resolution/engine.py": "https://github.com/metrology-atlas/vitaldb-resolution/blob/main/vitaldb_resolution/engine.py"
        }
      }
    },
    "value-model-weight": {
      "id": "value-model-weight",
      "name": "Rounding and heaping of weight",
      "summary": "Recorded weight is a mixture of 3 rounding modes (whole_kg, scale_0.1, scale_0.05); the model gives the true value's distribution for each record.",
      "date": "2026-10-13",
      "status": "current",
      "methods": [
        "value-resolution"
      ],
      "variables": [
        "weight"
      ],
      "model": "vitaldb-resolution/vitaldb_resolution/data/models/weight.model.json",
      "data": "- 6,388 values, none missing or zero; 5 values finer than 0.05 kg are flagged off-grid.\n- Fitted to adults, first case per patient (6,041). 137 of 237 repeat patients have the same weight in every case.\n- 57 children get native resolution (flag `child_native_resolution`).\n",
      "code": [
        "vitaldb-resolution/vitaldb_resolution/engine.py"
      ],
      "tags": [
        "generated",
        "vitaldb-resolution"
      ],
      "figures": [
        {
          "id": "observed-vs-model",
          "file": "models/weight.png",
          "theme": "heaping",
          "source": "metrology-atlas datasets/vitaldb/_import/value_distributions.py",
          "caption": "Every recorded value (blue lines) against what the fitted model predicts (orange dots): its rounding rules applied to its true-value density. Heaps on round numbers are the rounding modes.",
          "alt": "Spike histogram of recorded values with model predictions overlaid; spikes on round numbers."
        }
      ],
      "results": "[[figure:observed-vs-model]]\n\n- Median latent weight is 58.3 kg for a woman aged 40 and 68.2 kg for a man, about 8% lower at 75.\n- The Student-t beats the log-normal by 66 BIC points.\n- A department effect on the scale share improves BIC (77,941 to 77,930). The finer scale seems to be used mainly on general surgery wards.\n- A half-kilogram mode and an emergency effect on whole kilograms did not help.\n- The expected share of each two-decimal ending matches the data, and the engine reproduces the log-likelihood (−38,917.2 against −38,917.3).\n",
      "references": [
        "khosla1964bodyweight",
        "vivanti2013weight"
      ],
      "background": "- Weight is mostly a mix of two scale resolutions rather than human rounding.\n- About 30% of weights come from a scale that reads to 0.05 kg and the rest from one that reads to 0.1 kg; about 3% are whole kilograms written in.\n",
      "method": "- Digits were screened at the finest observed resolution (two decimals).\n- Latent: log-t regression on sex and age (quadratic). The Student-t was compared with a log-normal.\n- Each device resolution is a mode, and its share may depend on department.\n- Tested: department effect on the scale share, a half-kilogram mode, and an emergency effect on whole kilograms.\n",
      "discussion": "- The first screen suggested a 0.2 kg scale, because even tenths looked more common. That was an artefact: rounding .x5 values to one decimal sends them to the even neighbour. At two decimals there is no even-digit excess, and the hypothesis was dropped.\n- The lesson went into the checklist: screen digits at the finest observed resolution only, never after rounding.\n- Measured weights get SD 0.5% of body weight (Khosla & Billewicz 1964); inpatients weighed at the same time each day vary by 0.4 kg or less (Vivanti et al. 2013).\n- Written-in weights are treated as self-reported: under-reported by 0.55 kg in men and 0.67 kg in women, SD about 2.7 kg (2018 Korea Community Health Survey).\n- The API needs sex, age and department.\n",
      "limitations": "- Clothing is not included in the measurement variability.\n- Measurement variability comes from the literature, not from this dataset.\n",
      "derived": {
        "variables": [
          "weight"
        ],
        "examples": [
          "oliguria-labels",
          "regression-with-rounding",
          "urine-output-per-kg-per-hour"
        ],
        "code_urls": {
          "vitaldb-resolution/vitaldb_resolution/engine.py": "https://github.com/metrology-atlas/vitaldb-resolution/blob/main/vitaldb_resolution/engine.py"
        }
      }
    },
    "value-registry-unmodelled": {
      "id": "value-registry-unmodelled",
      "name": "Blood products, age and ICU days",
      "summary": "Red-cell and plasma units, age and ICU days are not heaped and get registry entries with a few flags rather than rounding models.\n",
      "date": "2026-10-13",
      "status": "current",
      "methods": [
        "value-resolution"
      ],
      "data": "- Red-cell units: 352 non-zero, mostly 1–5 units. Plasma units: 129 non-zero, 1–52.\n- Age: 6,380 numbers, 8 coded \">89\".\n- ICU days: 0 in 81%, 1 in 13%, up to 179.\n",
      "method": "- Checked counts for smoothness and last digits for heaping.\n- Cross-checked red-cell units against blood loss for unit errors, and ICU days against the hospital stay.\n",
      "results": "- Red-cell units track blood loss (Spearman 0.68). Five values look like mL entered as units: 400 and 800, plus 50, 60 and 68 units with blood loss of 50–350 mL.\n- The red-cell rule (value ≥ 50 units and blood loss < 1,000 mL) flags exactly those 5 entries and leaves 59 and 69 units with 28–30 L blood loss alone.\n- Large plasma values go with very large blood loss and are not flagged.\n- Age: 19.2% end in 0 or 5 (20% expected). 10 infants have ages in tenths of a year (0.3–0.8).\n- 6 cases have more ICU days than the hospital stay (`order_violation`).\n- Since vitaldb-resolution v0.6.0 (18 October; model version 2): ICU days carry the flag `icu_exceeds_postop`\n  where the count reaches the days from the case-day midnight to the discharge midnight (36 cases by the\n  library's definition; [[observation:icu-days-exceed-postop-stay]]), and the counting rule is recorded as\n  assumed.\n",
      "discussion": "- Blood products are exact counts. Probable mL entries are flagged, not converted, since the pack volume is unknown.\n- \">89\" is top-coded and returns an open interval from 90 (status `top_coded`).\n- In-hospital death is binary and needs no model.\n",
      "limitations": "- Whether ages are completed years (floor) or rounded cannot be tested from the data. Floor is kept as a working assumption.\n- ICU days cannot be checked against ICU admission and discharge times, which are not in the dataset. Until confirmed it is a count with ±1 day uncertainty.\n",
      "open_questions": [
        {
          "title": "How ICU days are counted",
          "kind": "assumption",
          "summary": "The release does not say. The counts argue against calendar days touched (815 one-day stays against 98 two-day stays, where that rule would make 2 the commonest value), so the atlas reads them as nights in the ICU or completed days, which the data cannot tell apart. The VitalDB team could settle it."
        },
        {
          "title": "Age as completed years",
          "kind": "assumption",
          "summary": "Age is taken as completed years (floor). The release documents only \"years\"; whole-number adults, infants in tenths of a year and the cap at 89 point to an exact age cut down, but the data cannot test it. Rounded age would shift every value by half a year."
        }
      ],
      "tags": [
        "from the resolution work"
      ],
      "derived": {
        "observations": [
          "icu-days-exceed-postop-stay"
        ]
      }
    },
    "ventilator-breath-matching": {
      "id": "ventilator-breath-matching",
      "name": "The Primus ventilator against SNUADC, by breaths",
      "summary": "Each breath shows in the Primus airway pressure and, through the chest, in SNUADC's central venous pressure. Tracking that lag puts the Primus on the SNUADC timeline to about 55 ms over the whole release, with a line per stretch between its AWP pauses (49 ms in the pilot). At scale it measures the Primus drifting a median +104 ppm against the recorder PC between its AWP pauses, with steps at the pauses that do not reset it.",
      "date": "2026-10-12",
      "status": "open",
      "methods": [
        "shared-events"
      ],
      "techniques": [
        "windowed-cross-correlation",
        "null-controls"
      ],
      "clocks": [
        "primus-clock",
        "snuadc-sample"
      ],
      "devices": [
        "primus",
        "snuadc"
      ],
      "variables": [
        "primus-awp",
        "primus-co2",
        "snuadc-art"
      ],
      "data": "20 of the longest benchmark cases with CVP, airway pressure and ECG; a 0/60/115/170 ppm sweep on 150 cases.",
      "background": "The Primus shares no artefact with SNUADC, but ventilation reaches the circulation: each breath moves central\nvenous and arterial pressure. Ventilation is very regular, so a plain cross-correlation matches one breath away\nalmost as well as at the right lag.\n",
      "method": "SNUADC times were corrected with the stamp model. Breath-onset-triggered averages of airway pressure and CVP in\n10-minute windows gave the lag. Breath identity was then confirmed by breath matching: the size of each breath's\nCVP response against the peak airway pressure of breath i + k, which varies enough from breath to breath to pick\nthe right k. Injected errors of one or two breaths tested it.\n",
      "results": "- **The Primus clock drifts uncorrected between its stamp gaps:** the AWP→CVP lag ramps within each stretch at a\n  median +92 ppm (IQR 34–153), and 72% of lag steps fall at Primus stamp jumps.\n- **Breath-triggered averages** give the lag to 59 ms median per window (p90 269 ms, including whole-breath\n  mis-assignments after resets).\n- **Breath matching** confirms the breath identity in 53% of good windows and recovers injected ±1–2-breath errors\n  91–96% of the time when confident. With confirmed identity the lag fits per-stretch lines to 49 ms (p90 165 ms).\n- **The drift is 31–200 ppm** (median +141) and not constant within a case. A sweep of assumed drifts is\n  inconclusive: 170 ppm fits no better than 115.\n- **At scale (19 October).** The same lag, on the SNUADC stamp model, in 1,061 recordings with CVP\n  ([[example:ppv-by-breath]]): the Primus sits a median 1.8 s off the SNUADC (p10–p90 0.6 to 5.1 s), and a line\n  through each whole recording gives +87 ppm (p10–p90 −49 to +229). Fitted with one slope and one offset per stretch\n  between AWP pauses, in 178 recordings with pauses ([[code:vitaldb-bench/tools/primus_drift_probe.py]]): the drift\n  within stretches is a median **+104 ppm** (p10–p90 +28 to +184), and the per-stretch offsets halve the residual\n  (195 to 99 ms). The steps at the 390 pauses (median −29 ms, p10–p90 −685 to +494 ms) are unrelated to the drift\n  built up in the stretch before (correlation 0.0): the pauses move the stamps but do not reset the drift.\n- **Accuracy over the whole release (19 October;\n  [[code:vitaldb-bench/tools/primus_sync_accuracy.py]]).** Each 10-minute window predicted from all the others\n  (a slope and an offset in each stretch between AWP pauses): a median error of 55 ms (p90 187 ms, p95 284 ms)\n  in 1,067 recordings with CVP, against 74 ms (p90 259 ms) with one slope per recording\n  ([[code:vitaldb-bench/tools/primus_pause_aware.py]]). The arterial mean pressure as a second reference is\n  noisier (83 ms; p90 343 ms, 1,384 recordings),\n  sometimes a breath out; where both exist (462) their drifts differ by a median 49 ppm. An injected shift of\n  0.25 s and drift of 100 ppm (196 recordings) come back as 0.252 s and 99.6 ppm (medians, one slope per\n  recording).\n\n[[figure:sync-demo-breath]]\n",
      "discussion": "The SNUADC stamp model places the SNUADC on the recorder PC's timeline, so the lag's slope is the Primus drift\nagainst the PC: about +104 ppm, close to the 115 ppm assumed earlier from this pilot. The Primus's own small\ncorrections ([[study:correction-periods]]) imply only 1–11 ppm, so they do not track the drift. The measured lag\nincludes the physiological airway-to-CVP delay, which is constant within a recording and does not affect the\nslope.\n",
      "limitations": "A 20-case pilot. The offset includes the physiological airway-to-CVP delay.",
      "open_questions": [
        {
          "title": "What sets the steps at the AWP pauses",
          "kind": "phenomenon",
          "summary": "Partly answered by [[study:primus-pause-steps]] (2,491 pauses): where CO2 carries on through the AWP pause the stamps barely step (median −1 ms, p10–p90 −164 to +187 ms); the large steps, up to seconds, come where CO2 stops too (26% of pauses). None undoes the drift built up. Open: what the stamps re-anchor to when both waveforms stop."
        },
        {
          "title": "Separating the airway-to-CVP delay",
          "kind": "next step",
          "summary": "The physiological delay from airway pressure to CVP is inside the measured offset and has not been separated."
        }
      ],
      "code": [
        "vitaldb-bench/tools/vent_sync_pilot.py",
        "vitaldb-bench/tools/primus_drift_probe.py",
        "vitaldb-bench/examples/ppv_by_breath.py"
      ],
      "figures": [
        {
          "id": "sync-demo-breath",
          "file": "sync-demo-1216.png",
          "theme": "example traces",
          "source": "vitaldb-bench tools/sync_demo.py",
          "records": [
            1216
          ],
          "caption": "One recording ([[case:1216]], 8.7 hours), before synchronisation (left) and after (right), on the same scales. Top: each clock's time minus the synchronised time. The SNUADC's stamps drift and are pulled back by half a second about every two hours; the BIS's stamps sit about 0.8 s off and are pushed forward every 8,192 s; the Primus drifts about 4 s over the recording and steps at its airway-pressure pauses (dotted lines). After synchronisation the typical error left is about 3 ms (SNUADC), 42 ms (BIS) and 55 ms (Primus, whole-release medians; bands). Middle: the same electrocautery and muscle bursts seen in the ECG and the EEG, as recorded and on the synchronised times, a check independent of the correction. Bottom: the airway-to-central-venous pressure lag of the breath cycle, as recorded and, after, as the error left when each 10-minute window is predicted from all the others.",
          "alt": "Six panels over 9 hours. Left column: sawtooth lines for SNUADC and BIS and a rising line for the Primus spanning about 3.5 seconds; burst lags ramping between minus 0.6 and minus 1.2 seconds; breath lags rising from minus 2 to plus 2 seconds. Right column: the same quantities flat near zero."
        },
        {
          "id": "vent-breath-matching",
          "file": "vent-breath-matching.png",
          "theme": "example traces",
          "source": "ad hoc (no script in vitaldb-bench yet; to be regenerated by one)",
          "records": [
            4644,
            4817,
            3148,
            750,
            2443,
            1403,
            3706,
            5780,
            2838,
            1936,
            3146,
            3549,
            2172,
            629
          ],
          "caption": "Lag of the Primus ventilator against SNUADC, from CVP after breath matching, in 14 cases. The lag ramps by about 50–200 ppm and steps at Primus stamp jumps (red dashed lines). [[case:1936]] and [[case:3549]] show offset plateaus near 8 s; in [[case:3549]] and [[case:2172]] red crosses mark values before the fix.",
          "alt": "Fourteen small scatter plots of lag in ms against hours, with filled and open dots and red dashed vertical lines."
        }
      ],
      "derived": {
        "parameters": [
          "primus-drift-breath"
        ],
        "examples": [
          "ppv-by-breath"
        ],
        "code_urls": {
          "vitaldb-bench/tools/vent_sync_pilot.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/vent_sync_pilot.py",
          "vitaldb-bench/tools/primus_drift_probe.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/primus_drift_probe.py",
          "vitaldb-bench/examples/ppv_by_breath.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/examples/ppv_by_breath.py"
        }
      }
    },
    "vitaldb-loader": {
      "id": "vitaldb-loader",
      "name": "The vitaldb package's loading conventions",
      "summary": "The vitaldb Python package sets time zero from the tracks loaded, places waveform blocks by rounding their stamps up to a sample, and lets overlapping blocks overwrite each other. Each choice moves or loses samples.",
      "date": "2026-10-17",
      "status": "current",
      "techniques": [
        "prediction-test"
      ],
      "methods": [
        "source-review",
        "stamp-reading"
      ],
      "clocks": [
        "snuadc-sample",
        "recorder-pc"
      ],
      "devices": [
        "vital-recorder",
        "snuadc"
      ],
      "variables": [
        "snuadc-ecg-ii",
        "snuadc-art",
        "snuadc-pleth"
      ],
      "data": "[[software:vitaldb-python|vitaldb]] 1.7.2 (VitalFile and get_track_samples in utils.py); checked on 6 files and on the 482 arrhythmia\ndatabase cases; device start delays across all 6,388 files. Work of the R-peak timing session, consolidated here.\n",
      "method": "The loader's code was read for how it sets time zero and places samples, and each behaviour reproduced on files:\nsubset against full loads, the stamp-minus-count error at each block, and the waveform rebuilt by sample count.\n",
      "results": "- **Time zero follows the loaded tracks.** VitalFile(path, tracks) sets its start to the first record among those\n  tracks; with all tracks it is the file's first record (the EVENT track in every file). Device start delays after\n  the file's first record: SNUADC 1.56 s median (0.44–4.5 s; maximum 10,879 s), Primus 0.72 s, BIS 0.95 s.\n- **Blocks are placed by rounding up.** Each wave record goes to sample index ceil((stamp − start) × rate). With\n  the stamps running −0.95 ppm against the count ([[study:stamp-arithmetic]]), each time the error crosses a whole\n  sample the next block is written one sample early and overwrites the last sample of the previous one: one real\n  sample lost about every 35 minutes, and everything after it 2 ms early on the array index. Rebuilt by count, the\n  2 ms phase steps disappear.\n- **Overlaps overwrite.** At each SNUADC reset the overlapping blocks are written over each other, discarding about\n  0.48 s of signal besides the time jump; sorting by stamp instead scrambles the order\n  ([[study:reset-cluster-order]]).\n",
      "discussion": "Clock rates measured on the package's array index read about +0.95 ppm high. Analyses that use the stamps directly\nare unaffected. The [[code:timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/connector.py|TimeNet import]] reads whole files, keeps every sample, places them by count and puts the reset\nrecords in sampling order.\n",
      "code": [
        "vitaldb-python/vitaldb/utils.py"
      ],
      "references": [
        "vitaldb-python"
      ],
      "derived": {
        "parameters": [
          "snuadc-start-delay"
        ],
        "observations": [
          "loaded-subset-time-zero",
          "loaders-lose-samples",
          "snuadc-stamp-arithmetic"
        ],
        "code_urls": {
          "vitaldb-python/vitaldb/utils.py": null
        }
      }
    }
  },
  "observations": {
    "art-with-pleth-factors": {
      "id": "art-with-pleth-factors",
      "name": "Three ART tracks carry the PLETH conversion factors",
      "summary": "In [[case:818]], [[case:5175]] and [[case:5693]] the SNUADC/ART track holds arterial pressure, but its header gives the PLETH conversion (0.394983 per code, offset −198.2504) instead of the ART one (0.987457 mmHg per code, offset −495.626). Converted as stored, the pressure reads about 2.5 times too low.",
      "status": "established",
      "variables": [
        "snuadc-art",
        "snuadc-pleth"
      ],
      "devices": [
        "snuadc"
      ],
      "consequence": "Read with the stored factors, the median arterial pressure in these cases is 28, 25 and 36 mmHg; read with the ART\nfactors it is 69, 62 and 89 mmHg, matching the monitor's own mean arterial pressure (Solar8000/ART_MBP: 70, 63 and 87\nmmHg). The raw codes are an ordinary arterial waveform (medians 572, 565 and 592, against 578 for ART over the\nrelease). None of the three cases has a SNUADC/PLETH track.\n",
      "handling": "The [[code:timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/scales.py|TimeNet import]]\nconverts these three tracks with the ART factors (code × 0.987457 − 495.626 mmHg) by default\n(VITALDB_SCALE_FIX=on) and keeps the stored factors in the metadata. With other readers, convert them by hand.\nAcross the release, the Solar8000 ART numerics are 2.35–2.38 × the waveform read with the stored factors, as\nexpected for arterial pressure stored with the PLETH factors. Found by the signal survey (every conversion\nfactor of every track).\n",
      "derived": {}
    },
    "arterial-steps-at-line-events": {
      "id": "arterial-steps-at-line-events",
      "name": "Arterial timing steps cluster at line events",
      "summary": "About a quarter of the steps in ECG→arterial-pressure timing fall within a minute of a line access or a cuff occlusion, against 4.5% at random times.",
      "status": "established",
      "variables": [
        "snuadc-art",
        "snuadc-ecg-ii"
      ],
      "studies": [
        "arterial-line-accesses"
      ],
      "consequence": "Arterial pressure is a clean timing reference only away from accesses and occlusions.",
      "derived": {}
    },
    "bis-eeg-cardiac-artefact": {
      "id": "bis-eeg-cardiac-artefact",
      "name": "The BIS EEG carries an averaged cardiac artefact",
      "summary": "Averaged on the ECG's R peaks, the BIS EEG shows the heartbeat (median 4.3 µV), near-simultaneous with the R wave. Single beats are too weak to match one by one.",
      "status": "likely",
      "devices": [
        "bis",
        "snuadc"
      ],
      "variables": [
        "bis-eeg",
        "snuadc-ecg-ii"
      ],
      "studies": [
        "bis-cardiac-artefact"
      ],
      "supersedes": [
        "bis-eeg-no-heartbeat"
      ],
      "consequence": "A second SNUADC–BIS timing reference for cases without bursts. Shown on 30 cases.",
      "derived": {}
    },
    "bis-eeg-no-heartbeat": {
      "id": "bis-eeg-no-heartbeat",
      "name": "The BIS EEG carries no heartbeat",
      "summary": "Claimed after a first search found no consistent cardiac trace in the BIS EEG. The search window was in the wrong place: the BIS stamps sit about 0.8 s off the ECG's.",
      "status": "withdrawn",
      "devices": [
        "bis"
      ],
      "variables": [
        "bis-eeg"
      ],
      "derived": {
        "superseded_by": [
          "bis-eeg-cardiac-artefact"
        ]
      }
    },
    "bis-index-slow": {
      "id": "bis-index-slow",
      "name": "The BIS index is slow",
      "summary": "The BIS index averages 15–60 s of EEG ending about 3 s before its stamp, with half its weight older than 30 s. It cannot time anything faster than about a minute.",
      "status": "established",
      "variables": [
        "bis-index",
        "bis-eeg"
      ],
      "devices": [
        "bis"
      ],
      "studies": [
        "bis-index-delay",
        "openeeg-bis-mimic"
      ],
      "consequence": "Use features computed from the EEG itself for event timing; the index suits trends over minutes.",
      "handling": "The bis_smoothing label gives each case's smoothing group.",
      "derived": {}
    },
    "cardioq-flow-segmented": {
      "id": "cardioq-flow-segmented",
      "name": "CardioQ FLOW uses three scales (a segmented code)",
      "summary": "CardioQ FLOW values step by 1 from 0 to 255, by 2 from 256 to 510 and by 4 from 512 to 1016: 509 values, a 9-bit code whose step doubles in each upper segment, as in µ-law and A-law companding or a small floating-point format (an exponent and a 7-bit mantissa). Below 256, even values outnumber odd ones by 40 to 230 times.",
      "status": "likely",
      "devices": [
        "cardioq"
      ],
      "studies": [
        "track-value-resolution"
      ],
      "consequence": "The resolution depends on the value, so each sample's quantisation uncertainty does too: step 1 below 256, 2\nfrom 256, 4 from 512 (1 SD = step/√12: 0.29, 0.58 and 1.15 units). Below 256 the even-value comb makes the\neffective step mostly 2 (1 SD 0.58). A per-sample uncertainty model must take the step from the value, not one\nstep per track.\n",
      "handling": "Not modelled yet. The FLOW unit and the device's encoding are not documented in the release; the Deltex\ninterface specification would settle both.\n",
      "figures": [
        {
          "id": "cardioq-flow-segmented",
          "file": "cardioq-flow-segmented.png",
          "theme": "distributions of recorded values",
          "source": "vitaldb-bench tools/value_obs_figs.py",
          "caption": "Left: every stored CardioQ FLOW value over the 29 recordings, one line per value, log count. The spacing doubles at 256 and again at 512. Right: values 100–140: even values (blue) hold about a hundred times more samples than odd ones (orange).",
          "alt": "Left, a dense comb of lines from 0 to 1016 whose spacing widens at 256 and 512. Right, tall blue bars on even values and short orange bars on odd ones."
        }
      ],
      "derived": {}
    },
    "case-ids-not-chronological": {
      "id": "case-ids-not-chronological",
      "name": "Case ids carry no time order",
      "summary": "Dates are shifted and case ids are shuffled: neighbouring ids are no more alike in device drift or port than random pairs.",
      "status": "established",
      "studies": [
        "drift-units"
      ],
      "consequence": "Unit ageing, moves or replacements cannot be followed through the release.",
      "derived": {}
    },
    "co2-cardiac-trace": {
      "id": "co2-cardiac-trace",
      "name": "A heartbeat-locked trace in the CO2 waveform that ignores the Primus clock",
      "summary": "Averaged on the ECG's R peaks, the Primus CO2 waveform shows a heartbeat-locked signal in 60% of 10-minute windows (30-recording pilot), tight to about 20 ms. Unlike the breath, its lag does not drift with the Primus clock: it stays fixed on the SNUADC timeline.",
      "status": "open",
      "clocks": [
        "primus-clock",
        "snuadc-sample"
      ],
      "devices": [
        "primus"
      ],
      "variables": [
        "primus-co2"
      ],
      "studies": [
        "airway-heartbeat-pilot"
      ],
      "description": "A cardiogenic oscillation in expired CO2 is physiological, but it would arrive on the Primus's stamps and drift\nwith them. A trace fixed to the SNUADC timeline suggests something timed outside the Primus clock: electrical\ncrosstalk from the ECG, or a component of the signal that is re-timed at the recorder. Neither has been tested.\n",
      "consequence": "The trace cannot be used to time the Primus. An analysis that looks for cardiac effects in CO2 should expect a\nheartbeat-locked component whose timing does not follow the Primus's other data.\n",
      "handling": "None yet; flagged for anyone using CO2 at beat resolution.",
      "derived": {}
    },
    "cuff-occludes-line": {
      "id": "cuff-occludes-line",
      "name": "The blood pressure cuff often sits on the arterial line's arm",
      "summary": "Many \"lost pulse\" events in the arterial pressure are an NIBP cuff inflating on the same arm: the pulse fades and pressure decays to 35–50 mmHg, then returns as the cuff cycle ends.",
      "status": "established",
      "devices": [
        "solar8000",
        "snuadc"
      ],
      "variables": [
        "snuadc-art",
        "solar8000-nibp"
      ],
      "studies": [
        "arterial-line-accesses"
      ],
      "consequence": "Cuff inflations disturb arterial timing as much as line accesses do.",
      "handling": "[[software:lineaccess]] labels them occlusions (art_line:occlusion), separately from accesses.",
      "code": [
        "lineaccess"
      ],
      "derived": {
        "code_urls": {
          "lineaccess": "https://github.com/metrology-atlas/lineaccess"
        }
      }
    },
    "device-drift-by-setup": {
      "id": "device-drift-by-setup",
      "name": "Device clock drift depends on the setup",
      "summary": "The drift of the SNUADC's and the BIS's timestamps against the recorder PC is largely set by the setup: knowing a recording's PC key (the serial ports of its four core devices) puts the SNUADC's drift within 3 ppm of its setup's median in 97% of cases and the BIS's in 83%, against 34% and 17% for the release's median. On the whole release's correction intervals the same test gives 71% and 60% ([[study:setup-identification]]).",
      "status": "established",
      "clocks": [
        "snuadc-sample",
        "bis-clock",
        "recorder-pc"
      ],
      "devices": [
        "snuadc",
        "bis",
        "vital-recorder"
      ],
      "studies": [
        "drift-units",
        "clock-correction-scan",
        "setup-identification"
      ],
      "description": "Drift per case from [[study:clock-correction-scan]], grouped by the serial ports each recording's devices were\nattached to (groups with at least 5 cases; the register is on [Recording setups](../setups.md)). Share of cases\nwithin 3 ppm of their group's median:\n\n| Grouping | SNUADC, step mode | BIS |\n|---|---|---|\n| The device's own port | 75% | 74% |\n| SNUADC and BIS ports together | 96% | — |\n| PC key (four core devices' ports) | 97% | 83% |\n| None (the release's median) | 34% | 17% |\n\nOne port is a weak key because Windows numbers ports per PC: the SNUADC was on COM4 in 2,824 recordings, on\nseveral PCs, and with six inferred units. The four ports together rarely repeat across PCs.\n",
      "consequence": "A drift quoted over all recordings (in the correction scan's cases, SNUADC medians −47 to −80 ppm by port, BIS 18 to 51 ppm) overstates the\nuncertainty for a recording whose setup is known, and understates how different two setups are.\n",
      "handling": "Where a track has no whole tooth to measure its own rate, use its setup's rate (the stamp model already uses the\nport's typical rate for the SNUADC), with the setup's spread as its uncertainty; fall back to a coarser key, then\nto the release, only when the PC key is incomplete. The per-setup rates are listed on\n[[parameter:snuadc-step-rate]] and [[parameter:bis-rate]], and in the register on [Recording setups](../setups.md).\n",
      "derived": {}
    },
    "duplicated-records": {
      "id": "duplicated-records",
      "name": "Eight recordings store whole messages twice",
      "summary": "In 8 recordings 15–48% of the waveform records are byte-for-byte copies of the record before them, with the same timestamp: [[case:15]], [[case:364]], [[case:1946]], [[case:3865]], [[case:4857]], [[case:5414]], [[case:5595]] and [[case:5619]]. SNUADC, Primus and BIS are affected equally, so the recorder, not a device, wrote them twice.",
      "status": "established",
      "studies": [
        "record-messages-and-steps"
      ],
      "devices": [
        "vital-recorder",
        "snuadc",
        "primus",
        "bis"
      ],
      "variables": [
        "snuadc-ecg-ii",
        "snuadc-art",
        "snuadc-pleth",
        "primus-co2",
        "primus-awp",
        "bis-eeg"
      ],
      "supersedes": [
        "snuadc-overlapping-records"
      ],
      "consequence": "Read in file order, the duplicated messages play twice: a 0.2 s SNUADC record (0.48 s Primus, 0.125 s BIS) repeats,\nthe samples outrun the stamps, and anything that counts samples to keep time drifts. In [[case:5595]], 40% of every\ndevice's records are copies (89,956 SNUADC records identical to the one before). Isolated single copies also occur in\nabout 500 other recordings (under 0.1% of their records).\n",
      "handling": "Drop a record that is identical to the previous one and carries the same timestamp. The copies are found by the step\nbetween records being exactly minus one message, then confirmed by comparing contents.\n",
      "code": [
        "vitaldb-bench/tools/record_scan.py",
        "vitaldb-bench/tools/record_steps_collect.py"
      ],
      "figures": [
        {
          "id": "duplicated-records",
          "file": "duplicated-records.png",
          "theme": "data continuity",
          "source": "vitaldb-bench tools/value_obs_figs.py",
          "caption": "One recording's Primus AWP, 40 consecutive records. Left: the stamp interval between records alternates 0 and 0.48 s (one message). Right: twelve records drawn at their stamps; each second record (orange, drawn 0.6 higher so both show) is an exact copy of the one before it.",
          "alt": "Left, dots alternating between 0 and 0.48 s. Right, a breath pressure trace drawn twice, the orange copy offset slightly above the blue."
        }
      ],
      "derived": {
        "code_urls": {
          "vitaldb-bench/tools/record_scan.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/record_scan.py",
          "vitaldb-bench/tools/record_steps_collect.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/record_steps_collect.py"
        }
      }
    },
    "eeg-ecg-offset-by-mode": {
      "id": "eeg-ecg-offset-by-mode",
      "name": "The EEG–ECG offset is fixed by the SNUADC correction mode",
      "summary": "After the stamp model, BIS EEG still lags the SNUADC ECG by a constant: −804 ms in step mode and −660 ms in frequent-correction mode over the whole release, tight within a port or unit. The mode, a property of the setup, sets the constant.",
      "status": "established",
      "clocks": [
        "snuadc-sample",
        "bis-clock"
      ],
      "devices": [
        "snuadc",
        "bis"
      ],
      "variables": [
        "bis-eeg",
        "snuadc-ecg-ii"
      ],
      "studies": [
        "snuadc-stamp-sawtooth",
        "loop-closure"
      ],
      "description": "Pilot, 117 step-mode and 29 frequent-mode cases: using the constant of the case's mode (leave-one-out) lines a\ntypical window up to 33 ms (median; p90 116 ms), against 38 ms (p90 154 ms) with one constant for all recordings\nand 16 ms with the case's own constant from cardiac artefact bursts. Whole release (2,710 recordings, 19 October):\n−804 ms in step mode (1,526), −660 ms in frequent-correction mode (78) and −766 ms with no SNUADC correction\n(1,099); the mode's constant lines a window up to 42 ms (p90 150 ms), the recording's own to 17 ms (p90 79 ms). Why the constant differs between modes is open\n([[study:loop-closure]]).\n",
      "consequence": "One EEG–ECG constant for all recordings is wrong by about 110 ms for the minority in frequent-correction mode.",
      "handling": "The stamp model can apply the constant of the recording's mode instead of one for all; the setup register says which setups use frequent-correction mode.",
      "derived": {}
    },
    "emr-offset-confounded": {
      "id": "emr-offset-confounded",
      "name": "The EMR clock's offset cannot be separated from documentation lag",
      "summary": "Both routes between the EMR clock and the recorder run through the case start, so any EMR clock offset cancels. Without a time from outside the release it is confounded with how late entries are made.",
      "status": "established",
      "clocks": [
        "hospital-records",
        "recorder-pc"
      ],
      "devices": [
        "clinical-records"
      ],
      "variables": [
        "emr-events"
      ],
      "studies": [
        "loop-closure",
        "emr-rounding"
      ],
      "consequence": "Only one constraint remains: entries rarely precede the device events they document.",
      "derived": {}
    },
    "emr-rounded-up": {
      "id": "emr-rounded-up",
      "name": "Anaesthesia record times on 5-minute marks are rounded up",
      "summary": "An anaesthesia start or end entered on a 5-minute mark was rounded up to it: the event happened in the 5 minutes before, and on average a little earlier still. Clear for anaesthesia end; for anaesthesia start the margin is small, and the value model ([[study:value-model-anestart]]) prefers rounding to the nearest mark.",
      "status": "established",
      "clocks": [
        "hospital-records"
      ],
      "devices": [
        "clinical-records"
      ],
      "variables": [
        "emr-events"
      ],
      "studies": [
        "emr-rounding",
        "value-model-anestart",
        "value-model-aneend"
      ],
      "consequence": "Taking a 5-minute entry at face value puts the event about 2.5 minutes late on average.",
      "handling": "The clinical_time labels give each time its interval and the earliest moment it could be known.",
      "derived": {}
    },
    "event-track-generated": {
      "id": "event-track-generated",
      "name": "The EVENT track repeats the clinical table",
      "summary": "In 411 random cases the recorder EVENT track's four entries equal the clinical table's case start, operation start, operation end and case end exactly: the release generated them from the table.",
      "status": "established",
      "devices": [
        "vital-recorder",
        "clinical-records"
      ],
      "variables": [
        "emr-events",
        "case-end"
      ],
      "studies": [
        "loop-closure"
      ],
      "consequence": "The EVENT track is not an independent record of when those events happened.",
      "derived": {}
    },
    "fiducial-quantisation": {
      "id": "fiducial-quantisation",
      "name": "Every time is quantised, and fiducials inherit it",
      "summary": "A time on a grid of step Δ carries an error of Δ/√12 (1 SD). In VitalDB that is 0.58 ms for a 500 Hz sample, 2.3 ms for BIS EEG, 0.8 ms for the arrhythmia annotations' 1/360 s grid, and up to ±17 ms for PPG fiducials taken on raw samples, which lock to the 33.3 ms frame.",
      "status": "established",
      "variables": [
        "snuadc-pleth",
        "snuadc-ecg-ii",
        "bis-eeg"
      ],
      "studies": [
        "time-zeros",
        "ppg-frame-clock"
      ],
      "description": "| Time | Grid | 1 SD |\n|---|---|---|\n| SNUADC sample (500 Hz) | 2 ms | 0.58 ms |\n| BIS EEG sample (128 Hz) | 7.8 ms | 2.3 ms |\n| Arrhythmia annotations | 1/360 s | 0.80 ms |\n| Numeric record stamps | 1 ms (0.1 ms in about 17% of files) | 0.29 ms |\n| PPG fiducial on raw samples, locked to the frame | up to 33.3 ms | up to about 9.6 ms |\n| Clinical-table entries | 1 or 5 min | 17 s or 87 s |\n\nWhich PPG fiducials lock depends on how flat the pulse is where they sit: the raw systolic maximum locks most\n(interval resultant at 33.3 ms: 0.38), the raw foot and steepest rise less (0.10), and the steepest upstroke on\nthe filtered signal hardly at all (0.02). Consecutive inter-beat intervals show no coarser grid for any detector.\n",
      "consequence": "Timing differences smaller than the grid's SD cannot be resolved from single events; averaging over n events\ndivides the random part by √n. Sub-sample interpolation helps only where the signal is smooth and not\nframe-quantised.\n",
      "handling": "The PPG correction uses the steepest upstroke on the filtered signal, which does not lock. The planner adds each\nsignal's interval/√12 to its timing error.\n",
      "code": [
        "physiosync/physiosync/quantisation.py"
      ],
      "references": [
        "ruffolo2025high"
      ],
      "derived": {
        "examples": [
          "pat-vs-bp",
          "pulse-arrival-time"
        ],
        "code_urls": {
          "physiosync/physiosync/quantisation.py": "https://github.com/metrology-atlas/physiosync/blob/main/physiosync/quantisation.py"
        }
      }
    },
    "hr-truncated": {
      "id": "hr-truncated",
      "name": "Solar8000 heart rate is truncated, not rounded",
      "summary": "The Solar8000 heart rate drops its fraction (floor), in 24 of 24 pilot cases; the respiratory rate rounds.",
      "status": "established",
      "variables": [
        "solar8000-hr"
      ],
      "studies": [
        "numeric-derivation-pilot"
      ],
      "consequence": "A heart rate computed from R–R intervals reads about 0.7% higher unless it is truncated the same way.",
      "derived": {}
    },
    "hr-two-delay-groups": {
      "id": "hr-two-delay-groups",
      "name": "Solar8000 heart rate comes one poll early or late",
      "summary": "Heart rate reaches the recorder about 1.9 s or about 4.2 s after its window ends, one 2 s poll apart. The group belongs to the recording PC and stays fixed through a case.",
      "status": "established",
      "devices": [
        "solar8000",
        "vital-recorder"
      ],
      "variables": [
        "solar8000-hr"
      ],
      "studies": [
        "hr-delay-groups"
      ],
      "consequence": "A single HR delay is wrong by about 2 s in a third of cases.",
      "handling": "The hr_delay_group label gives each case's group; the PC gives a prior for uncertain cases.",
      "derived": {}
    },
    "icu-days-exceed-postop-stay": {
      "id": "icu-days-exceed-postop-stay",
      "name": "Some ICU stays are longer than the postoperative stay",
      "summary": "In 37 cases the ICU day count reaches or exceeds the days from surgery to discharge, though the release describes it as the postoperative ICU stay. Most are emergencies with long stays before surgery, so the count probably includes ICU days before the operation.",
      "status": "likely",
      "clocks": [
        "hospital-records"
      ],
      "devices": [
        "clinical-records"
      ],
      "variables": [
        "icu-days"
      ],
      "studies": [
        "value-registry-unmodelled"
      ],
      "description": "Days from surgery to discharge are counted from the midnight of the case day to the discharge midnight. Of 1,204\ncases with an ICU stay:\n\n| ICU days minus postoperative days | Cases | Emergency | In-hospital death | Median days before surgery |\n|---|---|---|---|---|\n| below 0 (expected) | 1,167 | 21% | 2% | 2 |\n| 0 | 6 | 67% | 33% | 8 |\n| 1 | 17 | 65% | 29% | 2 |\n| 2 or more | 14 | 64% | 57% | 16.5 |\n\nOne of the 14 has an admission time about 42,650 days before surgery, a placeholder date rather than a stay.\n",
      "consequence": "For these cases the ICU count is not a postoperative quantity, and the 17 cases one day over cannot be used to\ndecide the counting rule.\n",
      "handling": "Flag ICU days greater than or equal to the postoperative stay; do not use them as postoperative ICU length.",
      "derived": {}
    },
    "imputed-op-ends": {
      "id": "imputed-op-ends",
      "name": "Op end was filled in from the recording end in 537 cases",
      "summary": "In 537 cases (8.4%) the op end is exactly 10 minutes before the end of the recording, so it was filled in rather than charted. These cases are concentrated in thoracic surgery and under sedation or spinal anaesthesia.",
      "status": "established",
      "clocks": [
        "hospital-records"
      ],
      "devices": [
        "clinical-records"
      ],
      "variables": [
        "opend"
      ],
      "studies": [
        "timestamp-imputed-and-conformance",
        "value-model-opend"
      ],
      "description": "The rule: recording end minus op end lies between 598 and 604 s. Shares by group:\n\n| Group | Cases | Imputed |\n|---|---|---|\n| Thoracic surgery | 1,111 | 16.6% |\n| General surgery | 4,930 | 6.9% |\n| Gynaecology | 230 | 3.5% |\n| Urology | 117 | 3.4% |\n| Sedation | 72 | 41.7% |\n| Spinal | 273 | 30.0% |\n| General anaesthesia | 6,043 | 7.0% |\n| Emergency | 782 | 9.8% |\n| Elective | 5,606 | 8.2% |\n",
      "consequence": "Op end, operation duration and emergence time are not measured in these cases, and leaving them out biases any\nanalysis towards general-anaesthesia, non-thoracic cases.\n",
      "handling": "The value model returns status imputed for them; analyses should report how many cases they drop and from which groups.",
      "derived": {}
    },
    "interleaved-records": {
      "id": "interleaved-records",
      "name": "In some recordings two unrelated streams are interleaved in one track",
      "summary": "In 7 recordings 64–95% of the records alternate between two streams: each record continues the one two before it, not the one before, and the two streams do not match each other. SNUADC and BIS in [[case:2219]], [[case:2379]], [[case:2640]], [[case:4836]], [[case:5327]] and [[case:5617]]; SNUADC in [[case:1939]]. In [[case:94]], [[case:945]], [[case:1132]], [[case:3195]] and [[case:5700]] 4–7% of SNUADC records interleave.",
      "status": "open",
      "studies": [
        "record-messages-and-steps"
      ],
      "devices": [
        "vital-recorder",
        "snuadc",
        "bis"
      ],
      "variables": [
        "snuadc-ecg-ii",
        "snuadc-art",
        "snuadc-pleth",
        "bis-eeg"
      ],
      "supersedes": [
        "snuadc-overlapping-records"
      ],
      "consequence": "The timestamps alternate (13 / 187 ms or 23 / 177 ms between SNUADC records, pairs summing to one 0.2 s message), and\nthe file holds about twice as many samples as its time span allows. Read in file order, the track switches between two\nsignals every 0.2 s. Joining each record to the one two before gives two continuous signals (boundary jump 0 codes,\nagainst 11 to the neighbouring record); their correlation is 0.10 and 0.01 in the two cases tested, with different\namplitudes, so they are not two copies of one signal. ECG II, ART and PLETH interleave together, and BIS does too, so\nthe recorder merged two sources. Which stream belongs to the patient is not known; in one case one stream's rhythm\nmatches the monitor's heart rate better.\n",
      "handling": "Treat these tracks as unusable until the streams are separated and attributed. A single run of backward steps that\npair to one message marks an interleaved stretch.\n",
      "code": [
        "vitaldb-bench/tools/record_scan.py",
        "vitaldb-bench/tools/record_steps_collect.py"
      ],
      "figures": [
        {
          "id": "interleaved-records",
          "file": "interleaved-records.png",
          "theme": "data continuity",
          "source": "vitaldb-bench tools/value_obs_figs.py",
          "caption": "One recording's SNUADC ECG II. Top: eight consecutive records in file order; the signal jumps at every 0.2 s record boundary. Bottom: joining every other record gives two continuous ECG signals that differ in amplitude and beat timing.",
          "alt": "Top, an ECG trace with a step at every 0.2 s boundary. Bottom, two smooth ECG traces, blue and orange, with beats at different times."
        }
      ],
      "derived": {
        "code_urls": {
          "vitaldb-bench/tools/record_scan.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/record_scan.py",
          "vitaldb-bench/tools/record_steps_collect.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/record_steps_collect.py"
        }
      }
    },
    "lab-reporting-limits": {
      "id": "lab-reporting-limits",
      "name": "Three lab analytes stop at a reporting limit",
      "summary": "Lactate is capped at 15.0 mmol/L, aPTT at 400.0 s, and ESR does not go below 2 mm/h: the values at the limit stand for \"this or beyond\", not for the number itself.",
      "status": "likely",
      "devices": [
        "clinical-records"
      ],
      "variables": [
        "lac",
        "aptt",
        "esr"
      ],
      "studies": [
        "heaping-screen-all-variables"
      ],
      "description": "| Analyte | Limit | Results at the limit | Cases | Most at any nearby value |\n|---|---|---|---|---|\n| Lactate | 15.0 mmol/L (top) | 317 | 28 | 16 |\n| aPTT | 400.0 s (top) | 226 | 87 | 8 |\n| ESR | 2 mm/h (bottom) | 89 | 72 | 32 |\n\nEach limit holds many times more results than any neighbouring value, the pattern of an analyser's reportable\nrange rather than of physiology.\n",
      "consequence": "Treated as exact numbers, these values understate the most abnormal results: a lactate of \"15\" may be 20.\n",
      "handling": "Treat them as censored (an open interval beyond the limit), as the registry does for ages above 89.",
      "derived": {}
    },
    "lab-time-is-specimen": {
      "id": "lab-time-is-specimen",
      "name": "The lab time is neither the draw nor the result",
      "summary": "VitalDB's lab timestamp marks the specimen (logged at the lab, or entered from a theatre analyser): minutes after the draw and before the result, whose time is not recorded.",
      "status": "established",
      "clocks": [
        "hospital-records"
      ],
      "devices": [
        "clinical-records"
      ],
      "variables": [
        "lab-blood-gas",
        "lab-central"
      ],
      "studies": [
        "lab-timestamp-meaning",
        "arterial-line-accesses"
      ],
      "consequence": "A model that uses a lab value at its timestamp sees the future. The physiology it describes happened 3–30 minutes\nearlier.\n",
      "handling": "Lab labels carry available_at = lab time plus an assumed turnaround, and a lab_draw label marks the matched draw.",
      "derived": {
        "examples": [
          "gas-gradients-at-the-draw"
        ]
      }
    },
    "loaded-subset-time-zero": {
      "id": "loaded-subset-time-zero",
      "name": "Time zero depends on the tracks loaded",
      "summary": "A file's time zero is its first record of any track. Loading only some tracks moves the zero to their first record: seconds later, and up to hours.",
      "status": "established",
      "devices": [
        "vital-recorder"
      ],
      "studies": [
        "time-zeros",
        "vitaldb-loader"
      ],
      "consequence": "Times \"from the start\" taken from different track subsets are not on one axis. The arrhythmia database's\nannotations count from the full file's zero.\n",
      "handling": "The [[code:timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/connector.py|TimeNet import]] reads whole files and keeps the full-file zero (vital_origin_s).",
      "derived": {
        "parameters": [
          "snuadc-start-delay"
        ]
      }
    },
    "loaders-lose-samples": {
      "id": "loaders-lose-samples",
      "name": "Stamp-based loaders lose samples at overlaps",
      "summary": "Loaders that place samples by their stamps onto a grid overwrite samples where stamps overlap: about 0.48 s at every SNUADC reset, and one sample about every 35 minutes from the stamp arithmetic.",
      "status": "likely",
      "devices": [
        "snuadc"
      ],
      "variables": [
        "snuadc-ecg-ii",
        "snuadc-art",
        "snuadc-pleth"
      ],
      "studies": [
        "stamp-arithmetic",
        "reset-cluster-order",
        "vitaldb-loader"
      ],
      "consequence": "The vitaldb package's to_numpy discards about half a second of signal at each reset, besides the time jump, and\nsorting by stamp scrambles sample order at overlaps.\n",
      "handling": "The [[code:timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/connector.py|TimeNet import]] keeps every sample and places them by count.",
      "derived": {}
    },
    "numeric-delays-per-case": {
      "id": "numeric-delays-per-case",
      "name": "Numeric delays are fixed within a case but differ between cases",
      "summary": "A numeric's delay does not drift through a recording, but 40–70% of its variance lies between cases. One delay per case describes it; one delay for the whole dataset is wrong by up to about 2 s.",
      "status": "established",
      "devices": [
        "solar8000",
        "primus"
      ],
      "variables": [
        "solar8000-hr",
        "solar8000-art-numerics",
        "primus-numerics"
      ],
      "studies": [
        "numeric-delay-variability"
      ],
      "handling": "Per-case delay labels (numeric_delay) with a standard error and a quality flag.",
      "derived": {}
    },
    "numerics-not-front-stamped": {
      "id": "numerics-not-front-stamped",
      "name": "Monitor numerics describe the past, never the future",
      "summary": "Every monitor numeric describes a window of 3–30 s or more that ended 1–9 s before its stamp. None is stamped before its data exist, so the stamps already respect the availability rule.",
      "status": "established",
      "devices": [
        "solar8000",
        "primus",
        "bis"
      ],
      "variables": [
        "solar8000-hr",
        "solar8000-art-numerics",
        "primus-numerics",
        "bis-index",
        "solar8000-spo2",
        "solar8000-pleth-hr"
      ],
      "studies": [
        "numeric-derivation-pilot",
        "numeric-delay-variability",
        "bis-index-delay"
      ],
      "consequence": "Aligning a numeric with the waveform it came from means shifting it back by its delay and spreading it over its\nwindow. Left at its stamp, it is safe for real-time models but late for physiology.\n",
      "handling": "The [[code:timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/connector.py|TimeNet import]] labels each numeric track with its typical delay and window (numeric_lag), and per-case labels\ncarry each case's own delay. Stored times are never moved.\n",
      "derived": {
        "examples": [
          "gas-gradients-at-the-draw"
        ]
      }
    },
    "oximeter-frame-clock-fingerprint": {
      "id": "oximeter-frame-clock-fingerprint",
      "name": "The oximeter's frame clock is a fingerprint of the setup",
      "summary": "The rate of the oximeter's 30 Hz frame clock against the SNUADC sample clock is fixed per recording setup (+7 to +92 ppm across setups) and repeats within about 2 ppm inside a recording, so it identifies the hardware a recording was made on.",
      "status": "established",
      "clocks": [
        "oximeter-frame-clock",
        "snuadc-sample"
      ],
      "devices": [
        "solar8000",
        "snuadc"
      ],
      "variables": [
        "snuadc-pleth"
      ],
      "studies": [
        "ppg-frame-clock",
        "setup-identification"
      ],
      "description": "Found in 189 of 266 cases; the two halves of a recording agree within 2 ppm (median). Examples: +84 ppm (IQR\n82–88) for one SNUADC port in step mode, +39 ppm for the same port in frequent-correction mode, +72 and +24 ppm\non two others. The same port giving two rates in the two modes shows that a port alone is not a setup.\n\nA scan of 1,350 recordings on 18 October (up to 30 per PC key) found the frame clock in 656. Within a PC key, 89%\nof them lie within 3 ppm of the key's median, against 19% within 3 ppm of the release's median; the key medians\nrun from +7 to +92 ppm ([[parameter:pleth-frame-clock-rate]], [[study:setup-identification]]). Two keys hold two\ndistinct rates, probably two oximeter modules on one PC.\n",
      "consequence": "A recording's frame clock can assign it to a setup without any port information, or split recordings that share\na port number but were made on different hardware.\n",
      "handling": "Measured from PLETH fiducials taken on raw samples (`physiosync.frame_clock`); see [Recording setups](../setups.md).",
      "derived": {}
    },
    "pleth-30hz-frames": {
      "id": "pleth-30hz-frames",
      "name": "The plethysmogram is built in 30 Hz frames",
      "summary": "The SNUADC PLETH waveform comes from the oximeter in 33.3 ms frames, smoothed to 500 Hz. Its content changes once per frame, on the oximeter's own clock, about 0.4 s after the finger.",
      "status": "established",
      "clocks": [
        "snuadc-sample"
      ],
      "devices": [
        "solar8000",
        "snuadc"
      ],
      "variables": [
        "snuadc-pleth"
      ],
      "consequence": "Pulse arrival time from VitalDB's PLETH is about 0.4 s longer than physiology and carries a sawtooth of frame\nslips; raw-sample PPG fiducials lock to the frame grid.\n",
      "handling": "[[software:physiosync]] removes the sawtooth; the planner shifts PLETH by its chain delay.",
      "studies": [
        "ppg-frame-clock",
        "ppg-sawtooth",
        "ppg-offset-reference",
        "monitor-lines"
      ],
      "code": [
        "physiosync/physiosync/frames.py"
      ],
      "derived": {
        "examples": [
          "pulse-arrival-time"
        ],
        "code_urls": {
          "physiosync/physiosync/frames.py": "https://github.com/metrology-atlas/physiosync/blob/main/physiosync/frames.py"
        }
      }
    },
    "ppg-hardware-delay": {
      "id": "ppg-hardware-delay",
      "name": "The PLETH arrives about 0.4 s late",
      "summary": "Raw R-to-PPG-foot pulse arrival time in VitalDB has a median of 0.64–0.71 s, about 0.4 s more than physiology: the monitor's buffering and filtering. One delay fits the whole release: no clusters by setup, SNUADC port or case order ([[study:pat-offset-census]]).",
      "status": "established",
      "variables": [
        "snuadc-pleth"
      ],
      "devices": [
        "solar8000"
      ],
      "studies": [
        "ppg-offset-reference",
        "ppg-sawtooth",
        "pat-offset-census"
      ],
      "consequence": "PAT and any ECG–PPG timing from VitalDB is mostly device delay unless that delay is removed. PLETH also lags ART by\n0.40–0.53 s, depending on the fiducial.\n",
      "handling": "One global delay, 385 ms ([[parameter:pleth-chain-delay]]: the stored R-to-foot delay of 655 ms minus an adopted physiological PAT of 270 ms), is subtracted from every recording, which keeps the differences between patients.",
      "derived": {
        "examples": [
          "pulse-arrival-time"
        ]
      }
    },
    "ppg-sawtooth": {
      "id": "ppg-sawtooth",
      "name": "The ECG→PPG delay is a sawtooth",
      "summary": "Inside the oximeter the PPG content creeps 18–52 ppm against its 30 Hz frames; every 11–31 minutes a frame repeats and the delay jumps up by 33 ms. The rate is fixed per recording setup.",
      "status": "established",
      "clocks": [
        "oximeter-frame-clock"
      ],
      "devices": [
        "solar8000",
        "snuadc"
      ],
      "variables": [
        "snuadc-pleth"
      ],
      "studies": [
        "ppg-sawtooth",
        "ppg-art-reference"
      ],
      "consequence": "Uncorrected, pulse arrival time from VitalDB carries a 0–33 ms sawtooth (about 9.5 ms RMS) on top of its hardware\ndelay; trends in PAT over tens of minutes can be the sawtooth, not physiology.\n",
      "handling": "[[software:physiosync]] removes it tooth by tooth; labels give each tooth's confidence tier.",
      "code": [
        "physiosync/physiosync/ecg_ppg.py",
        "physiosync/physiosync/labels.py"
      ],
      "derived": {
        "examples": [
          "pulse-arrival-time"
        ],
        "code_urls": {
          "physiosync/physiosync/ecg_ppg.py": "https://github.com/metrology-atlas/physiosync/blob/main/physiosync/ecg_ppg.py",
          "physiosync/physiosync/labels.py": "https://github.com/metrology-atlas/physiosync/blob/main/physiosync/labels.py"
        }
      }
    },
    "ppg-sawtooth-pauses": {
      "id": "ppg-sawtooth-pauses",
      "name": "The PPG sawtooth sometimes stops",
      "summary": "In some stretches, and some whole recordings, no frame slip comes although several were due. The delay there is flat and the frame clock keeps running; what stops the content's drift is not known.",
      "status": "open",
      "clocks": [
        "oximeter-frame-clock"
      ],
      "variables": [
        "snuadc-pleth"
      ],
      "studies": [
        "ppg-sawtooth-pauses"
      ],
      "consequence": "A stretch without slips is not automatically safe: some show slow drift or fast ramps instead.",
      "handling": "Stretches that pass the paused-sawtooth test are labelled N (offset only).",
      "derived": {
        "examples": [
          "pulse-arrival-time"
        ]
      }
    },
    "primus-awp-pauses": {
      "id": "primus-awp-pauses",
      "name": "The Primus AWP stops for about 21 s every 7,230 s",
      "summary": "In 5,502 recordings the Primus airway-pressure waveform has gaps of 19–23.5 s (whole messages of 0.48 s, mostly 21.12 s) that recur every 7,230 s, the Primus's correction interval; a clock correction follows about 21.6 s after. The CO2 waveform carries on through 90% of them. The 21 s of AWP are lost, not merely re-stamped.",
      "status": "established",
      "clocks": [
        "primus-clock"
      ],
      "devices": [
        "primus"
      ],
      "variables": [
        "primus-awp",
        "primus-co2"
      ],
      "studies": [
        "device-correction-schedules",
        "step-classification",
        "primus-pause-steps"
      ],
      "code": [
        "vitaldb-bench/tools/correction_schedules.py",
        "vitaldb-bench/tools/primus_pause_test.py"
      ],
      "description": "**Lost, not re-stamped.** CO2 and AWP come from the same device and the same breaths, so their breathing patterns\nline up at a small, fixed lag. For each pause, that lag was measured in the 2 minutes before it (median 1 s), and\nthe correlation in the 2 minutes after it was compared at the same lag (as if AWP data were lost and the stamps\nare right) and at the same lag plus the pause length (as if no data were lost and only the stamps jumped). In\n9,122 pauses with data on both sides, 8,393 had AWP and CO2 correlated before the pause; in the 5,232 (3,552\nrecordings) whose breathing rhythm does not make the two answers look alike, 96% fit lost data (median\ncorrelation 0.69 against −0.35).\n\n[[figure:primus-awp-pauses]]\n",
      "consequence": "An AWP gap of about 21 s is the Primus's two-hourly resynchronisation: about 21 s of airway pressure is never\nrecorded. It is not a disconnection, and CO2 covers the same breaths. Within the Primus nothing needs re-timing\n(AWP keeps its lag to CO2), but against the recorder PC the stamps step at the pause: a median −1 ms (p10–p90 −164 to +187 ms) at the 74% of pauses where CO2\ncarries on, and by up to seconds at the 26% where CO2 stops too ([[study:primus-pause-steps]]), so a Primus–SNUADC offset fitted before a pause does not\ncarry over to after it.\n",
      "handling": "Treat it as a real gap. Flagged as a periodic device event in the step classes.",
      "figures": [
        {
          "id": "primus-awp-pauses",
          "file": "primus-awp-pauses.png",
          "theme": "data continuity",
          "source": "vitaldb-bench tools/primus_pause_test.py",
          "caption": "Each point is one AWP pause: after the pause, the correlation between AWP and CO2 at the lag measured before it (vertical: AWP data lost) against the correlation at that lag plus the pause length (horizontal: only the stamps jumped). Blue: pauses whose breathing rhythm separates the two answers (5,232); grey: pauses where the rhythm makes them alike. Almost all blue points lie above the diagonal.",
          "alt": "A scatter of about 8,000 points; the blue ones form a broad band at a vertical correlation near 0.7 across horizontal values from −1 to 0.5, above the diagonal."
        }
      ],
      "derived": {
        "examples": [
          "ppv-by-breath"
        ],
        "code_urls": {
          "vitaldb-bench/tools/correction_schedules.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/correction_schedules.py",
          "vitaldb-bench/tools/primus_pause_test.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/primus_pause_test.py"
        }
      }
    },
    "primus-waveform-gains": {
      "id": "primus-waveform-gains",
      "name": "Primus waveform gains are wrong in every recording, for CO2 or for AWP",
      "summary": "Recordings store the Primus waveforms with one of three sets of scale factors. Checked against the Primus's own ETCO2 and PIP numerics in all 6,388 recordings, CO2 fits only 0.075 mmHg per code and AWP only 0.1 mbar per code with offset −20, so most recordings overstate CO2 by a third, and the others overstate AWP by about 3% (950 recordings) or understate it about fourfold (26).",
      "status": "established",
      "devices": [
        "primus"
      ],
      "variables": [
        "primus-co2",
        "primus-awp",
        "primus-numerics"
      ],
      "studies": [
        "track-value-resolution"
      ],
      "references": [
        "vitaldb-docs-fileformat"
      ],
      "description": "Whole release (19 October 2026), the waveform's maximum or mean over the 7–10 s before each numeric; the\nmedian per recording of numeric ÷ waveform, then its median and 10th–90th percentiles over recordings:\n\n| Waveform, stored scale | Recordings | As stored | Rescaled to 0.075 (CO2) or 0.1 / −20 (AWP) |\n|---|---:|---|---|\n| CO2 at 0.1 | 5,392 | ETCO2 0.748 (0.747–0.750) | 0.998 (0.995–1.000) |\n| CO2 at 0.075 | 950 | ETCO2 0.998 (0.996–1.000) | (as stored) |\n| AWP at 0.1 / −20 | 5,077 | PIP 0.994 (0.989–1.000) | (as stored) |\n| AWP at 0.1064 / −22 | 910 | PIP 0.976 (0.965–0.992) | 0.995 (0.990–1.000) |\n| AWP at 0.01 / 0 | 24 | PIP 4.49 (3.23–5.36) | 0.995 (0.989–1.000) |\n\n0.748 is 0.075 / 0.1. Mean airway pressure agrees in the same way (0.992 to 0.993 after rescaling, in every\nconfiguration). The code is tools/numeric_vs_wave.py in vitaldb-bench; the device page lists each configuration\nwith its corrected factors.\n",
      "consequence": "In most recordings the CO2 waveform overstates CO2 by a third (a breath at ETCO2 35 mmHg peaks at about 47); in\n950 recordings AWP reads about 3% high at typical pressures, and in 26 it reads about a quarter of the true value. Anything computed from these waveforms\n(end-tidal values, capnogram shape, pressures) inherits the error; the Primus numerics are not affected.\n",
      "handling": "The [[code:timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/scales.py|TimeNet import]]\nstores the corrected factors by default (VITALDB_SCALE_FIX=on): CO2 0.075, AWP 0.1 / −20; the stored counts are\nunchanged and the file's factors are kept in the Signal's metadata (scale_stored, scale_correction). The Primus\ndevice page lists the corrected factors per configuration. Other readers (the vitaldb package, the raw file) still\nuse the stored factors.\n",
      "code": [
        "timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/scales.py",
        "vitaldb-bench/tools/numeric_vs_wave.py"
      ],
      "figures": [
        {
          "id": "primus-waveform-gains",
          "file": "primus-waveform-gains.png",
          "theme": "agreement and residuals",
          "source": "vitaldb-bench tools/value_obs_figs.py",
          "caption": "Per recording, the median of the Primus numeric divided by the waveform's peak over the 7 s before it. Left: ETCO2 against CO2: recordings stored with 0.1 sit at 0.75, those with 0.075 at 1; rescaled to 0.075, all sit at 1. Right: PIP against AWP: 0.1064 / −22 sits about 2.5% low; rescaled to 0.1 / −20, all sit at 1. Ratios below 0.6 are drawn in the first bin.",
          "alt": "Two log-count histograms of ratios; left, an orange peak at 0.75 and a blue peak at 1, with the rescaled outline at 1; right, overlapping peaks near 0.97 and 0.99 that move to 1 when rescaled."
        }
      ],
      "derived": {
        "code_urls": {
          "timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/scales.py": "https://github.com/OpenTSLM/TimeNet/blob/main/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/scales.py",
          "vitaldb-bench/tools/numeric_vs_wave.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/numeric_vs_wave.py"
        }
      }
    },
    "recording-pauses": {
      "id": "recording-pauses",
      "name": "Recording pauses of about 20 s",
      "summary": "In 29% of files every device jumps forward together, by a median 19.6 s: the recorder paused. Data are missing there, and the stamps show it honestly. The pauses run on a 5-hour timer: consecutive pauses in a recording are 5 h apart (plus the pause) in 86% of cases, and the first comes at any time after the recording starts.",
      "status": "established",
      "clocks": [
        "recorder-pc"
      ],
      "devices": [
        "vital-recorder"
      ],
      "studies": [
        "recorder-pc-steps",
        "continuity-gaps-segments"
      ],
      "description": "From the steps between records (19 October): 1,713 pauses of 15–25 s in the SNUADC with a BIS gap within 3 s, in\n1,605 recordings. Of the 108 intervals between two pauses in one recording, 93 (86%) lie between 17,990 and\n18,050 s, and only 3 are longer. The first pause comes a median 1.5 h after the SNUADC starts (10th–90th\npercentile 0.4–3.8 h), so the timer does not start with the recording, like the BIS and Primus correction timers\n([[study:device-correction-schedules]]). This is the 5-hour limit on SNUADC segments\n([[study:continuity-gaps-segments]]): a segment can only reach 5 h when it starts at a pause. The cause (a\n5-hour job on the recording PC, or in VitalRecorder) is not known.\n",
      "consequence": "Gaps common to every device are recorder pauses, not clock steps; nothing needs correcting. A recording longer\nthan 5 h that has had one pause will have another 5 h later.\n",
      "derived": {}
    },
    "release-time-shift": {
      "id": "release-time-shift",
      "name": "Every case starts at 2100-01-01 00:00 UTC",
      "summary": "The release shifts each case to the same start, removing the date and the time of day. Within a case all times keep their spacing.",
      "status": "established",
      "clocks": [
        "recorder-pc",
        "utc"
      ],
      "devices": [
        "vital-recorder"
      ],
      "studies": [
        "case-clock",
        "recorder-pc-steps"
      ],
      "consequence": "No timing claim against UTC can be tested in the release, and time of day, weekday and season are hidden. The\ntime of day can be recovered to the minute from the admission date and EMR times.\n",
      "handling": "The case clock ([[code:vitaldb-bench/vitaldb_bench/case_clock.py|vitaldb_bench.case_clock]]) recovers the time of day as KST.",
      "code": [
        "vitaldb-bench/vitaldb_bench/case_clock.py"
      ],
      "derived": {
        "code_urls": {
          "vitaldb-bench/vitaldb_bench/case_clock.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/vitaldb_bench/case_clock.py"
        }
      }
    },
    "reset-records-out-of-order": {
      "id": "reset-records-out-of-order",
      "name": "Five records out of order at every SNUADC reset",
      "summary": "At each step-mode reset the five records after it are written in the wrong order: two continue the old timing and three start the new, interleaved. Read in file order, or sorted by stamp, one second of signal is scrambled.",
      "status": "established",
      "clocks": [
        "snuadc-sample"
      ],
      "devices": [
        "snuadc",
        "vital-recorder"
      ],
      "variables": [
        "snuadc-ecg-ii",
        "snuadc-art",
        "snuadc-pleth"
      ],
      "studies": [
        "reset-cluster-order",
        "clock-correction-scan"
      ],
      "consequence": "Any reader that keeps file order or sorts by stamp (the vitaldb package included) scrambles about 1 s of every\nSNUADC channel at each reset, roughly every two hours of step-mode recording.\n",
      "handling": "The [[code:timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/connector.py|TimeNet import]] puts the records back in sampling order by default (VITALDB_RESET_ORDER), and the clock fits\nuse the same order; a fit and a build in different orders are not combined.\n",
      "code": [
        "timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/resets.py",
        "vitaldb-bench/vitaldb_bench/clock_model.py"
      ],
      "derived": {
        "code_urls": {
          "timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/resets.py": "https://github.com/OpenTSLM/TimeNet/blob/main/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/resets.py",
          "vitaldb-bench/vitaldb_bench/clock_model.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/vitaldb_bench/clock_model.py"
        }
      }
    },
    "reset-signal-lost": {
      "id": "reset-signal-lost",
      "name": "About 20 ms of signal lost at every SNUADC reset",
      "summary": "At each step-mode reset the acquisition pauses for about 20 ms inside the last record before the new timing, and that time is never sampled: 17.7 ms on the sample count ([[parameter:snuadc-reset-loss]]), once the filler sample is counted. Every record still holds 100 samples, so nothing in the file shows it.",
      "status": "established",
      "clocks": [
        "snuadc-sample"
      ],
      "devices": [
        "snuadc"
      ],
      "variables": [
        "snuadc-ecg-ii",
        "snuadc-art",
        "snuadc-pleth"
      ],
      "studies": [
        "paced-ecg",
        "reset-cluster-order",
        "snuadc-stamp-sawtooth",
        "snuadc-firmware"
      ],
      "consequence": "Timed by sample count, every sample after a reset is about 18 ms early. Over a case's one to three resets this adds\nup, and it biases drift rates estimated from correction sizes by about 3.6%.\n",
      "handling": "The stamp model adds 17.7 ms back at each step-mode reset, at the end of the record that holds the pause; samples\ninside that record can still be up to about 20 ms late.\n",
      "derived": {}
    },
    "reset-zero-sample": {
      "id": "reset-zero-sample",
      "name": "An extra zero sample after most resets",
      "summary": "Most SNUADC resets (335 of 392) contain one sample that reads 0 in every channel at once, a little after the reset. It carries no time of its own: the signal either side runs on as if it were not there.",
      "status": "established",
      "devices": [
        "snuadc"
      ],
      "variables": [
        "snuadc-ecg-ii",
        "snuadc-art",
        "snuadc-pleth"
      ],
      "studies": [
        "reset-cluster-order"
      ],
      "consequence": "A spike to the bottom of the range in every channel, and one sample more on the count than time passed. Detectors\nthat see it as a value will misfire; timing by count is off by 2 ms after it.\n",
      "handling": "Still stored as a value. Masking it is a pending decision.",
      "derived": {}
    },
    "small-snuadc-gaps": {
      "id": "small-snuadc-gaps",
      "name": "Small positive SNUADC gaps are real data loss",
      "summary": "Forward jumps of under a second in SNUADC stamps are missing data, not clock corrections: the beat interval across them fits the gap better than the samples butted together.",
      "status": "established",
      "devices": [
        "snuadc"
      ],
      "variables": [
        "snuadc-art",
        "snuadc-ecg-ii"
      ],
      "studies": [
        "clock-correction-scan"
      ],
      "consequence": "Treat them as gaps; joining across them shortens the time.",
      "derived": {}
    },
    "snuadc-100hz-cases": {
      "id": "snuadc-100hz-cases",
      "name": "Twelve recordings sample the SNUADC waveforms at 100 Hz",
      "summary": "In 12 of 6,388 recordings every SNUADC waveform (ECG II, ECG V5, PLETH, and ART or CVP where present) is stored at 100 Hz instead of 500 Hz: [[case:299]], [[case:803]], [[case:1150]], [[case:1693]], [[case:2362]], [[case:2920]], [[case:4458]], [[case:4582]], [[case:5066]], [[case:6119]], [[case:6133]] and [[case:6375]].",
      "status": "established",
      "variables": [
        "snuadc-ecg-ii",
        "snuadc-pleth",
        "snuadc-art"
      ],
      "devices": [
        "snuadc"
      ],
      "consequence": "Methods that assume 500 Hz (fiducial timing, the sawtooth fit, filters designed for 500 Hz) give wrong times or\nfail in these recordings; time resolution is 10 ms, not 2 ms. The conversion factors are the same as at 500 Hz.\n",
      "handling": "Read the sample rate from each track's header rather than assuming 500 Hz; the PAT example and the census skip recordings without 500 Hz ECG and PLETH. Found by the signal survey.",
      "derived": {}
    },
    "snuadc-channel-skew": {
      "id": "snuadc-channel-skew",
      "name": "SNUADC channels are read one after another",
      "summary": "The SNUADC firmware reads its 8 channels in turn, about 0.1 ms each, so within one sample the last channel is read up to about 0.8 ms after the first. The channel order is not known.",
      "status": "likely",
      "devices": [
        "snuadc"
      ],
      "variables": [
        "snuadc-ecg-ii",
        "snuadc-art",
        "snuadc-pleth"
      ],
      "studies": [
        "monitor-lines",
        "snuadc-firmware"
      ],
      "consequence": "A fixed sub-millisecond skew between SNUADC channels: a floor on same-clock alignment such as ECG to arterial\npressure. A monitor line seen in both ART and PLETH puts ART about 0.3–0.4 ms ahead.\n",
      "derived": {}
    },
    "snuadc-event-inputs": {
      "id": "snuadc-event-inputs",
      "name": "SNUADC has two event inputs",
      "summary": "The firmware sends two event-jack flags with every sample. If any case used them, they would be markers on the SNUADC's own clock.",
      "status": "open",
      "devices": [
        "snuadc"
      ],
      "studies": [
        "snuadc-firmware"
      ],
      "consequence": "Not yet checked whether any release track carries them.",
      "derived": {}
    },
    "snuadc-long-uncorrected": {
      "id": "snuadc-long-uncorrected",
      "name": "Long SNUADC recordings with no correction at all",
      "summary": "1,066 cases of 2.5 h or longer show no SNUADC correction, although at the step-mode median of −63 ppm a 0.49 s step falls due within about 2.2 h. Slower units, a third recorder mode, or both; shared bursts point to slower units (about −36 ppm where the BIS rate is known, [[study:snuadc-stamp-sawtooth]], due only after about 3.8 h). The Solar8000 route cannot settle it: its rates shrink toward zero in recordings under about 4 h ([[study:solar-snuadc-clock]]).",
      "status": "open",
      "devices": [
        "snuadc"
      ],
      "studies": [
        "clock-correction-scan",
        "drift-units",
        "solar-snuadc-clock"
      ],
      "consequence": "Step-mode files with no reset can carry up to about 0.4 s of drift that the stamps do not show. The first stamp\nmodel gave them a rate of 0; the adopted one (model3) gives them their port's step-mode rate, which leaves a\nspread of about −135 to +122 ms/h (p10–p90) because each recording drifts at its own rate\n([[study:snuadc-stamp-sawtooth]]).\n",
      "derived": {}
    },
    "snuadc-mode-setups": {
      "id": "snuadc-mode-setups",
      "name": "The SNUADC correction mode belongs to setups, not dates",
      "summary": "The frequent-correction mode is confined to a few setups, can switch either way within a case, and marks a period in at most one setup. It is not a global software change.",
      "status": "established",
      "clocks": [
        "snuadc-sample"
      ],
      "devices": [
        "snuadc"
      ],
      "studies": [
        "drift-units"
      ],
      "consequence": "The mode cannot date a recording, and must be read per case (or per stretch) before correcting.",
      "derived": {}
    },
    "snuadc-no-timestamps": {
      "id": "snuadc-no-timestamps",
      "name": "SNUADC sends no time; the recorder stamps by count",
      "summary": "The SNUADC board streams samples with no time stamps. Every SNUADC time in VitalDB is the recorder's model of the board's clock: the sample count at the nominal rate, pulled back when it drifts too far.",
      "status": "established",
      "clocks": [
        "snuadc-sample",
        "recorder-pc"
      ],
      "devices": [
        "snuadc",
        "vital-recorder"
      ],
      "variables": [
        "snuadc-ecg-ii",
        "snuadc-art",
        "snuadc-pleth"
      ],
      "studies": [
        "snuadc-firmware",
        "clock-correction-scan",
        "snuadc-stamp-sawtooth"
      ],
      "consequence": "Anything the board loses without telling the recorder (a stall, a pause) is invisible in the stamps. The stamp\nsawtooth is the recorder correcting its model, not the board.\n",
      "handling": "The stamp model refits the line; paced and continuity checks find what the stamps miss.",
      "derived": {}
    },
    "snuadc-one-clock": {
      "id": "snuadc-one-clock",
      "name": "All SNUADC channels share one clock",
      "summary": "Every SNUADC waveform track of a case (ECG leads, ART, PLETH, CVP) has the same first stamp, the same record timing and the same corrections. They are stamped together.",
      "status": "established",
      "clocks": [
        "snuadc-sample"
      ],
      "devices": [
        "snuadc"
      ],
      "variables": [
        "snuadc-ecg-ii",
        "snuadc-art",
        "snuadc-pleth"
      ],
      "studies": [
        "clock-correction-scan",
        "time-zeros",
        "reset-cluster-order"
      ],
      "consequence": "Clock drift cancels between SNUADC channels: ECG, arterial pressure and plethysmogram line up to the sample, apart\nfrom the analogue paths behind them. A beat-alignment check between two SNUADC leads tests sample packing, not\ndrift.\n",
      "derived": {
        "examples": [
          "pulse-arrival-time"
        ]
      }
    },
    "snuadc-overlapping-records": {
      "id": "snuadc-overlapping-records",
      "name": "In 34 files the SNUADC records overlap throughout",
      "summary": "In 34 files the SNUADC records step back over their neighbours far more often than resets explain: in 20 of them 4–95% of all record junctions overlap, by up to about 100 ms. The reset-order rule matches there by coincidence.",
      "status": "open",
      "devices": [
        "snuadc",
        "vital-recorder"
      ],
      "studies": [
        "reset-cluster-order"
      ],
      "consequence": "Read in file order, these files hold overlapping samples; reordered by the reset rule, they would be scrambled.\nTheir timing should not be trusted until the cause is known.\n",
      "handling": "The record-level signal survey (19 October) explains the worst of them: records written twice\n([[observation:duplicated-records]], 8 recordings) and two interleaved streams\n([[observation:interleaved-records]], 12 recordings), which also affect Primus and BIS. Drop the duplicates; treat\ninterleaved tracks as unusable. The [[code:timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/resets.py|reset-order rule]]\nstill needs a guard that skips such tracks before the next full build. None is in the current builds or clock\nfits.\n",
      "code": [
        "vitaldb-bench/tools/snuadc_stamp_census.py"
      ],
      "derived": {
        "superseded_by": [
          "duplicated-records",
          "interleaved-records"
        ],
        "code_urls": {
          "vitaldb-bench/tools/snuadc_stamp_census.py": "https://github.com/metrology-atlas/vitaldb-bench/blob/main/tools/snuadc_stamp_census.py"
        }
      }
    },
    "snuadc-stamp-arithmetic": {
      "id": "snuadc-stamp-arithmetic",
      "name": "SNUADC stamps run −0.95 ppm against the sample count",
      "summary": "Between corrections each 0.2 s record stamp increment is about 0.19 µs short: stamps run −0.95 ppm from the sample count in 88% of files (both SNUADC modes). It is arithmetic, not clock drift. The other 12% sit between −0.90 and −0.55 ppm.",
      "status": "established",
      "devices": [
        "snuadc",
        "vital-recorder"
      ],
      "studies": [
        "stamp-arithmetic",
        "paced-ecg",
        "vitaldb-loader"
      ],
      "consequence": "Rates read from stamps include −0.95 ppm; the frequent-correction mode's \"about −1 ppm\" is this, not drift.\nPlacing samples by rounded stamps, as the vitaldb package does, overwrites one sample about every 35 minutes.\n",
      "handling": "The [[code:timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/connector.py|TimeNet import]] places samples by count, so no sample is lost. The census of all files (17 October) confirmed it.",
      "code": [
        "timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/connector.py"
      ],
      "derived": {
        "code_urls": {
          "timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/connector.py": "https://github.com/OpenTSLM/TimeNet/blob/main/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/connector.py"
        }
      }
    },
    "snuadc-ten-bit-codes": {
      "id": "snuadc-ten-bit-codes",
      "name": "SNUADC values are 10-bit codes, though the device is documented as 12-bit",
      "summary": "Every SNUADC channel in the release stores codes 0–1023 and nothing above: 10 bits, a step of 0.0098746 V. The VitalRecorder device list describes the SNUADC as a 12-bit converter.",
      "status": "open",
      "devices": [
        "snuadc"
      ],
      "variables": [
        "snuadc-ecg-ii",
        "snuadc-art",
        "snuadc-pleth"
      ],
      "studies": [
        "track-value-resolution"
      ],
      "references": [
        "vitaldb-docs-devices",
        "vitaldb-docs-config"
      ],
      "consequence": "The resolution is the 10-bit step: 0.00987 mV for ECG, 0.987 mmHg for ART, CVP and FEM, 0.395 units for PLETH.\nAnyone assuming the documented 12 bits would expect steps four times finer than the data hold.\n",
      "handling": "The atlas uses the observed 10-bit step. Why the documented and stored resolutions differ is open: an older\n10-bit SNUADC, or 12-bit readings reduced to 10 bits before transmission.\n",
      "figures": [
        {
          "id": "snuadc-ten-bit-codes",
          "file": "snuadc-ten-bit-codes.png",
          "theme": "distributions of recorded values",
          "source": "vitaldb-bench tools/value_obs_figs.py",
          "caption": "Samples at each stored code over the whole release, one bin per code, for ECG II and ART. ECG reaches every code from 0 to 1023 and piles up at both ends (clipping); no SNUADC channel has a code above 1023, where a 12-bit converter would continue to 4095.",
          "alt": "Two log-scale code histograms from 0 to about 1000; ECG reaches 1023 with a spike there; the region above 1023 is empty and shaded."
        }
      ],
      "derived": {}
    },
    "snuadc-value-truncation": {
      "id": "snuadc-value-truncation",
      "name": "SNUADC values are truncated to the count below",
      "summary": "The SNUADC firmware truncates each calibrated value instead of rounding it, so every value sits half a count low on average (about 0.5 mmHg for arterial pressure). Values clamp at counts 0 and 1023.",
      "status": "likely",
      "devices": [
        "snuadc"
      ],
      "variables": [
        "snuadc-art",
        "snuadc-ecg-ii",
        "snuadc-pleth"
      ],
      "studies": [
        "snuadc-firmware"
      ],
      "consequence": "A small constant bias, and clipping at the monitor's ±4 V output limits (arterial pressure near 400 mmHg).",
      "derived": {}
    },
    "solar-relays-primus": {
      "id": "solar-relays-primus",
      "name": "The Solar8000 relays the Primus's ventilation numbers",
      "summary": "Solar8000 ETCO2, RR_CO2, VENT_PIP and VENT_RR change only every 6–8 s: they are Primus values passed on through the Solar8000's 2 s polling, not measured by the monitor.",
      "status": "established",
      "devices": [
        "solar8000",
        "primus"
      ],
      "studies": [
        "numeric-derivation-pilot"
      ],
      "consequence": "These tracks carry the Primus's timing plus the relay, and duplicate the Primus's own numerics.",
      "derived": {}
    },
    "solar-st-duplicated": {
      "id": "solar-st-duplicated",
      "name": "Solar8000 ST records are written twice",
      "summary": "Every Solar8000 ST_II record is duplicated at the same stamp (0–20 ms apart).",
      "status": "established",
      "devices": [
        "solar8000"
      ],
      "studies": [
        "numeric-derivation-pilot"
      ],
      "handling": "The [[code:timenet/packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/connector.py|TimeNet import]] drops a record that repeats the previous value within 50 ms, and counts the drops.",
      "derived": {}
    },
    "spo2-freezes": {
      "id": "spo2-freezes",
      "name": "SpO2 freezes in low perfusion",
      "summary": "During low perfusion SpO2 mostly holds its last value rather than blanking (55–70% of 5–20 s episodes); when the probe comes off it blanks within about a second.",
      "status": "established",
      "variables": [
        "solar8000-spo2",
        "solar8000-pleth-hr"
      ],
      "studies": [
        "spo2-latency"
      ],
      "consequence": "A flat SpO2 is not proof of a stable saturation; check the PLETH.",
      "derived": {}
    }
  },
  "methods": {
    "clinical-time": {
      "id": "clinical-time",
      "name": "Clinical and laboratory times",
      "summary": "Find out what a time in the clinical and lab tables marks (an event, its documentation, a rounding), using signatures the event leaves in the signals.",
      "order": 6,
      "description": "**The idea.** Anaesthesia record entries and laboratory results carry times made by other systems and by people.\nAn event often leaves a trace in the signals (an arterial blood draw in the pressure trace, the start of\nanaesthesia in the BIS and ventilation), and comparing the two shows what the recorded time stands for.\n\n**What it gives.** Rounding rules (5-minute entries rounded up), documentation lag, and what a lab timestamp marks\n(here, a per-specimen time minutes after the draw, not the result time).\n\n**How.** Detect the signal-side event independently, match it to the table entry, and fit how the recorded time\nrelates to it (nearest, floor, ceiling, plus a lag), with random times as the control.\n\n**Pitfalls.** Both sides may be measured from the same origin (the case start), so an offset can cancel and look\nlike agreement. Without a time from outside the release, a clock offset and a documentation lag cannot be told\napart.\n",
      "references": [
        "goodwin2022timing",
        "rietveld2002rounding"
      ],
      "derived": {
        "studies": [
          "value-model-aneend",
          "value-model-anestart",
          "value-model-opend",
          "value-model-opstart",
          "arterial-line-accesses",
          "case-clock",
          "dates-admission-discharge-los",
          "emr-rounding",
          "lab-timestamp-meaning",
          "timestamp-clock-reconstruction",
          "timestamp-derived-intervals",
          "timestamp-imputed-and-conformance",
          "timestamp-lab-times-meaning"
        ]
      }
    },
    "data-continuity": {
      "id": "data-continuity",
      "name": "Data continuity",
      "order": 4.5,
      "summary": "How completely each signal was recorded: where its gaps are, how long its unbroken stretches run, how it was packaged into messages, and whether a break in the stamps lost data or only moved the clock.",
      "techniques": [
        "censored-durations",
        "remainder-test",
        "junction-scores",
        "null-controls"
      ],
      "description": "**The idea.** A recorder writes each signal as a run of messages (records), each a fixed number of samples with\none time stamp. Between two records the stamps should advance by exactly one message. Any other step is either\ndata lost, data written twice or out of order, or a clock correction that moved the stamps without losing a\nsample. Each of these leaves its own signature, and the signatures can be told apart from the stamps and the\nsamples alone.\n\n**What it gives.** For every waveform: its message size, the distribution of its gaps and of its unbroken\nsegments, which gaps are system events shared by every device, which are lost whole messages, which are clock\nresynchronisations, and which recordings carry duplicated or interleaved records.\n\n**How.** Distributions are shown as cumulative counts (the number of gaps or segments longer than x) on log–log\naxes: losses that strike at random give a smooth curve, and a cliff or step marks a mechanism with its own\nduration. Segments cut short by the end of a recording are censored, not ended by a gap. Gaps are folded modulo\nthe message length; whole lost messages pile up at zero. Gap length against the time since the previous gap\nseparates resynchronisations, which grow with the elapsed time, from losses, which do not.\n\n**Pitfalls.** Backward steps outnumber forward ones by a hundred to one in some signals and are invisible to a\ngap rule that only looks forward. One recording with a defect can dominate a count across the release, so\nshares are reported per recording as well as in total.\n",
      "derived": {
        "studies": [
          "continuity-gaps-segments",
          "device-correction-schedules",
          "record-messages-and-steps",
          "step-classification"
        ]
      }
    },
    "delays-and-windows": {
      "id": "delays-and-windows",
      "name": "Delays and averaging windows",
      "summary": "Work out how long after the event a value is stamped, and what span of signal it summarises, by recomputing monitor numbers from the waveforms they come from.",
      "order": 5,
      "description": "**The idea.** A heart rate, a mean pressure or a depth-of-anaesthesia index describes a window of signal that\nended before its stamp. Recompute the number from the raw waveform with candidate algorithms and windows, and\nfind the window and the delay that best reproduce the stamped values.\n\n**What it gives.** For each numeric: the window it averages, the delay from the window's end to its stamp, and\nwhether that delay is fixed within a case and between cases (and so whether it needs a per-case label).\n\n**How.** A uniform model of time and value: each stamped value is the candidate statistic over a window ending\none delay before the stamp, plus noise; fit the delay per case by likelihood over a grid, with the availability\nrule enforced (a value cannot precede its window). Step events (a sudden desaturation) give onset latencies.\n\n**Pitfalls.** A longer window with a smaller delay can mimic a shorter one with a larger delay; the availability\nrule removes the impossible half. Device settings (an averaging option) are not recorded and must be inferred.\n",
      "references": [
        "goodwin2022timing"
      ],
      "derived": {
        "studies": [
          "bis-index-delay",
          "hr-delay-groups",
          "numeric-delay-variability",
          "numeric-derivation-pilot",
          "openeeg-bis-mimic",
          "pat-offset-census",
          "ppg-offset-reference",
          "rare-device-sync",
          "solar-snuadc-clock",
          "spo2-latency"
        ]
      }
    },
    "shared-events": {
      "id": "shared-events",
      "name": "One event, two clocks",
      "summary": "Find an event that two independently clocked signals both record, and read the offset between their clocks from where each puts it.",
      "order": 2,
      "techniques": [
        "windowed-cross-correlation",
        "beat-matching",
        "null-controls"
      ],
      "description": "**The idea.** Stamps say how a device drifts against the recorder, not where its time sits against another\ndevice. For that, both devices must see the same event: an electrical burst reaching ECG and EEG electrodes at\nonce, a breath moving airway pressure and venous pressure, a heartbeat in two monitors' pressure traces, a drug's\neffect following its infusion.\n\n**What it gives.** The offset between two devices' stamps, followed through a recording: its constant part (a\nlatency), its ramps (the difference of the two drifts) and its steps (corrections). Where a third route exists,\nthe offsets must close around the loop, which tests every assumption on the way.\n\n**How.** Turn each signal into something the event dominates (a band-power envelope, breath onsets, beat times);\ncross-correlate in sliding windows, or match events one to one with a delay window around the expected level;\nkeep only windows with a clear, unique peak; repeat everything at random times as a control.\n\n**Pitfalls.** Regular events (ventilation, steady heart rhythm) match one cycle away almost as well as at the right\nlag. Physiology adds its own delay: a breath reaches the veins later than the airway, so the offset mixes clock\nand body. Soft links such as drug onset only bound an offset, they do not measure it.\n",
      "derived": {
        "studies": [
          "airway-heartbeat-pilot",
          "bis-cardiac-artefact",
          "drug-effect-latency",
          "loop-closure",
          "numeric-clock-check",
          "ppg-art-reference",
          "primus-pause-steps",
          "rare-device-sync",
          "shared-bursts",
          "snuadc-stamp-sawtooth",
          "solar-snuadc-clock",
          "ventilator-breath-matching"
        ]
      }
    },
    "signal-borne-clocks": {
      "id": "signal-borne-clocks",
      "name": "Clocks carried in the signal",
      "summary": "Use a regular rhythm inside the recorded signal (a pacemaker, a monitor's frame clock, an output or mains line) as a third clock, independent of the clocks being tested.",
      "order": 3,
      "techniques": [
        "template-beat-timing",
        "phase-step-detection"
      ],
      "description": "**The idea.** Two clocks measured against each other give only a relative rate. A third clock that sits inside\nthe signal breaks the tie: it is sampled by one clock, stamped by another, and corrected by neither. A pacemaker\nat a fixed rate, the frame clock of a pulse oximeter, a line at a fixed frequency from a monitor's output stage:\neach keeps its own time whatever the recorder does.\n\n**What it gives.** Breaks in a timeline that no other method sees (time lost without a gap in the count), a\ndevice's rate against a clock outside the recording chain, and fingerprints of the hardware (frame and line rates\nfixed per setup).\n\n**How.** Time each event of the rhythm as precisely as possible (template matching, phase-locked fiducials,\ncomplex demodulation of a line), place the times on each timeline in turn (sample count, record stamps, corrected\nline), and look for steps in phase between stretches where the rhythm should run on.\n\n**Pitfalls.** The rhythm must truly be fixed: a demand pacemaker resets at every intrinsic beat, and a \"regular\"\nsinus rhythm wanders by milliseconds. Frame clocks show only where a fiducial locks to them. Such clocks are rare:\nseven of 6,388 cases are paced at a fixed rate.\n",
      "derived": {
        "studies": [
          "monitor-lines",
          "paced-ecg",
          "pat-offset-census",
          "ppg-art-reference",
          "ppg-frame-clock",
          "ppg-sawtooth-pauses",
          "ppg-sawtooth",
          "setup-identification",
          "snuadc-firmware"
        ]
      }
    },
    "signal-continuity": {
      "id": "signal-continuity",
      "name": "Does the waveform run on?",
      "summary": "Test whether a waveform continues smoothly across a boundary (a record junction, a reset, a gap) to tell lost samples from lost time, and to find samples written out of order.",
      "order": 4,
      "techniques": [
        "junction-scores",
        "null-controls"
      ],
      "description": "**The idea.** A sampled physiological waveform is smooth over a few samples. Where two records are joined, the\nstep from the last sample of one to the first of the next should look like any other step. A step too large for\nthe local slope means samples are missing or out of place; a slope that needs a gap of m samples to fit means time\npassed with nothing sampled.\n\n**What it gives.** Which boundaries hide data loss, which records are out of order (and the order that repairs\nthem), where unsampled time sits and how long it is, and spurious samples that carry no time.\n\n**How.** Score each junction against an extrapolation of the samples before it, scaled by the signal's ordinary\nsample-to-sample steps; try alternative orders; fit lines or quadratics either side of each sample boundary with\na time gap of 0 to m samples and keep the gap that fits best; compare with the same scores far from any boundary.\n\n**Pitfalls.** Sensitivity depends on slope: a gap on the flat part of a pulse is invisible, so only a fraction of\nevents can be located. Use several channels and steep signals (arterial upstrokes, the plethysmogram).\n",
      "derived": {
        "studies": [
          "arterial-line-accesses",
          "clock-correction-scan",
          "reset-cluster-order",
          "step-classification"
        ]
      }
    },
    "source-review": {
      "id": "source-review",
      "name": "Reading the source",
      "summary": "Read firmware, manuals and loader code to explain a timing behaviour seen in the data, or to predict one to test.",
      "order": 7,
      "techniques": [
        "prediction-test"
      ],
      "description": "**The idea.** Some timing behaviour is written down: in a device's firmware, a manual, or the code that loads the\ndata. Reading it explains a measurement and predicts the next test.\n\n**What it gives.** Mechanisms: why time is lost at a stall, why channels are skewed, why a loader moves time zero.\n\n**How.** Find the code path that produces or consumes the time stamps, state what it predicts, and test the\nprediction in the data.\n\n**Pitfalls.** The source may not be the version that was deployed, and documentation may describe settings rather\nthan what was used. A source is a hypothesis until the data agree.\n",
      "derived": {
        "studies": [
          "openeeg-bis-mimic",
          "ppg-offset-reference",
          "snuadc-firmware",
          "time-zeros",
          "vitaldb-loader"
        ]
      }
    },
    "stamp-reading": {
      "id": "stamp-reading",
      "name": "Reading the stamps",
      "summary": "Fit drift and corrections from the record time stamps themselves, against the number of samples each record holds.",
      "order": 1,
      "techniques": [
        "sawtooth-fit",
        "coincident-jumps",
        "unit-mixture"
      ],
      "description": "**The idea.** A recorder that receives blocks of samples stamps each block with a time. If the device sends no\ntime of its own, the recorder's stamps are its model of the device's clock: between corrections they advance by\nexactly the number of samples received at the nominal rate, and where the model has drifted too far from the\nrecorder's own clock, the recorder pulls it back. So the stamps, compared with the sample count, show the drift\nand every correction, with no signal content needed.\n\n**What it gives.** For each device: its correction rule (a drift threshold, a fixed timer, re-anchoring at gaps),\nits drift between corrections, and the size and timing of each correction. Rates cluster into physical units,\nand a term shared by every device in a recording reveals the recorder's own clock. Jumps that hit every device at\nonce are recorder events (pauses, clock steps).\n\n**How.** Split each track into runs that join to within half a sample; classify the jumps between runs by their\ndevice's signature and by whether every track of the device jumps together; fit a line through each stretch, a\nsawtooth across corrections, one rate per recording from whole teeth (correction to correction).\n\n**Pitfalls.** Stamps show only what the recorder knew. Time the device lost without telling the recorder (a\nsampling pause, a dropped sample) is invisible to them, and stamp arithmetic can masquerade as drift. A rate\nread from stamps is relative to the recorder, never absolute. Check the result against an event seen on both\nclocks ([[method:shared-events]]) or a clock carried in the signal ([[method:signal-borne-clocks]]).\n\n[[figure:error-by-level]]\n",
      "figures": [
        {
          "id": "error-by-level",
          "file": "stamp-model-error-by-level.png",
          "caption": "What reading the stamps buys: the EEG–ECG timing error in 10-minute windows (3,683 windows, 165 cases),\nchecked against bursts seen in both signals. From [[study:snuadc-stamp-sawtooth]].\n",
          "alt": "Bar chart of median and 90th-percentile timing error for five correction levels, falling from 792 and 1,005 ms for recorded stamps to 16 and 67 ms for the stamp model with a per-case constant.",
          "study": "snuadc-stamp-sawtooth",
          "placeholder": true
        }
      ],
      "derived": {
        "studies": [
          "clock-correction-scan",
          "correction-periods",
          "device-correction-schedules",
          "drift-units",
          "recorder-pc-steps",
          "reset-cluster-order",
          "setup-identification",
          "snuadc-stamp-sawtooth",
          "stamp-arithmetic",
          "time-zeros",
          "vitaldb-loader"
        ]
      }
    },
    "value-resolution": {
      "id": "value-resolution",
      "name": "Rounding, heaping and value resolution",
      "order": 8,
      "summary": "How far a recorded number is from the true one because of how it was written down: the resolution it was recorded at, and the coarser values people round to.",
      "description": "A recorded value is the true value passed through a rounding rule. When one rule applies to every record,\nthe true value lies uniformly within half a step of the recorded one. Values written by people are usually\n**heaped**: most records follow a fine rule, but some are rounded to 5, 10 or 100, and a recorded 100 mL\nmay be a measured 100 or anything from 50 to 150 rounded by hand.\n\nThe studies in this group fit each heaped variable as a **mixture of rounding modes** over a smooth\ndensity of true values. Each mode is a rule (nearest 5, nearest 10, rounded up ...) with its own share of\nthe records. Given a recorded value, the model returns the distribution of the true value: a mixture of\nthe intervals each mode could have produced it from, weighted by how likely each mode is to give that\nvalue. Values that a coarse mode can produce (multiples of 10, of 50 ...) therefore carry wider\ndistributions than values only the fine mode produces.\n\nCharted clock times are heaped the same way, as reported travel times are on multiples of 5, 15 and 30\nminutes ([[reference:rietveld2002rounding|Rietveld 2002]], who fitted the rounding probabilities and used Bayes'\nrule to recover the true time). Here they are modelled as an offset from a device time recorded to the second,\nso the time side and the value side are one uncertainty. The general framework for such data is coarsening\n([[reference:heitjan1989grouped|Heitjan 1989]], [[reference:heitjan1991coarse|Heitjan and Rubin 1991]]), and\nheaping as a mixture of rounding behaviours follows [[reference:wang2008heaping|Wang and Heitjan 2008]].\n\nThe models are fitted and evaluated by [[software:vitaldb-resolution]]. The atlas reads them from a\npinned snapshot of that library, so each page shows the fitted modes and shares without copying them.\n",
      "references": [
        "heitjan1989grouped",
        "heitjan1991coarse",
        "wang2008heaping",
        "rietveld2002rounding"
      ],
      "derived": {
        "studies": [
          "value-model-aneend",
          "value-model-anestart",
          "value-model-bmi",
          "value-model-height",
          "value-model-intraop-crystalloid",
          "value-model-intraop-ebl",
          "value-model-intraop-ppf",
          "value-model-intraop-uo",
          "value-model-opend",
          "value-model-opstart",
          "value-model-weight",
          "dates-admission-discharge-los",
          "distribution-api-validation",
          "heaping-screen-all-variables",
          "instrument-calibration",
          "lab-computed-value-grids",
          "lab-haematocrit-two-analysers",
          "lab-preop-pao2-decimal",
          "lab-timestamp-meaning",
          "timestamp-clock-reconstruction",
          "timestamp-derived-intervals",
          "timestamp-imputed-and-conformance",
          "timestamp-lab-times-meaning",
          "track-value-resolution",
          "value-bolus-drug-doses",
          "value-intraop-colloid-containers",
          "value-registry-unmodelled"
        ]
      }
    }
  },
  "techniques": {
    "beat-matching": {
      "id": "beat-matching",
      "name": "Beat matching",
      "summary": "Pair beats seen by two signals one to one, using a delay window around the known level rather than nearest neighbours, which flip between beats when the lag is near half a beat.",
      "derived": {
        "studies": [
          "pat-offset-census",
          "ppg-art-reference",
          "ppg-sawtooth",
          "rare-device-sync",
          "time-zeros"
        ],
        "methods": [
          "shared-events"
        ]
      }
    },
    "censored-durations": {
      "id": "censored-durations",
      "name": "Cumulative duration curves",
      "summary": "Plot the number of gaps or segments longer than x against x on log–log axes, with segments cut by the start or end of a recording treated as censored; cliffs and steps mark mechanisms with a duration of their own.",
      "description": "A histogram needs bins; the cumulative count does not, and keeps every value. Random losses give a smooth\ncurve, while a process with a fixed duration (a restart, a periodic break) gives a cliff at that duration.\nThree versions of the segment curve bracket the truth: all segments, interior segments only (a gap at both\nends), and a Kaplan–Meier estimate that counts edge segments as censored.\n",
      "derived": {
        "studies": [
          "continuity-gaps-segments"
        ],
        "methods": [
          "data-continuity"
        ]
      }
    },
    "coincident-jumps": {
      "id": "coincident-jumps",
      "name": "Coincident jumps",
      "summary": "Look for stamp jumps that appear in several independently clocked tracks at the same moment. A step of the common clock moves all of them; a device's own correction moves one.",
      "derived": {
        "studies": [
          "clock-correction-scan",
          "recorder-pc-steps"
        ],
        "methods": [
          "stamp-reading"
        ]
      }
    },
    "junction-scores": {
      "id": "junction-scores",
      "name": "Junction scores",
      "summary": "Score the step across a record junction against a linear extrapolation of the samples before it, scaled by the signal's ordinary sample-to-sample steps.",
      "derived": {
        "studies": [
          "clock-correction-scan",
          "reset-cluster-order",
          "step-classification"
        ],
        "methods": [
          "data-continuity",
          "signal-continuity"
        ]
      }
    },
    "null-controls": {
      "id": "null-controls",
      "name": "Null controls",
      "summary": "Repeat each test where the effect cannot be: at random times, at the same interval later, sign-flipped, or between two channels of one device. The effect is real only where it beats its control.",
      "derived": {
        "studies": [
          "airway-heartbeat-pilot",
          "arterial-line-accesses",
          "bis-cardiac-artefact",
          "clock-correction-scan",
          "continuity-gaps-segments",
          "drug-effect-latency",
          "emr-rounding",
          "lab-timestamp-meaning",
          "loop-closure",
          "ppg-art-reference",
          "ppg-frame-clock",
          "ppg-sawtooth-pauses",
          "ppg-sawtooth",
          "primus-pause-steps",
          "reset-cluster-order",
          "shared-bursts",
          "spo2-latency",
          "step-classification",
          "ventilator-breath-matching"
        ],
        "methods": [
          "data-continuity",
          "shared-events",
          "signal-continuity"
        ]
      }
    },
    "phase-step-detection": {
      "id": "phase-step-detection",
      "name": "Phase-step detection",
      "summary": "Fit a regular train before and after a candidate break and test for a step in its phase, accepting only steps without an intervening beat that could explain them.",
      "derived": {
        "studies": [
          "paced-ecg",
          "ppg-frame-clock",
          "ppg-sawtooth-pauses",
          "ppg-sawtooth"
        ],
        "methods": [
          "signal-borne-clocks"
        ]
      }
    },
    "prediction-test": {
      "id": "prediction-test",
      "name": "Predict, then test",
      "summary": "Turn what a source says into a prediction about the data (a step every 4,295 s, a skew between two channels, a sample lost every 35 minutes) and look for it, with a control; a source that is not borne out is set aside.",
      "derived": {
        "studies": [
          "openeeg-bis-mimic",
          "snuadc-firmware",
          "vitaldb-loader"
        ],
        "methods": [
          "source-review"
        ]
      }
    },
    "remainder-test": {
      "id": "remainder-test",
      "name": "Remainder test",
      "summary": "Fold each gap modulo the signal's message duration: whole lost messages pile up at zero, while clock corrections and partial losses spread over the whole interval.",
      "derived": {
        "studies": [
          "device-correction-schedules",
          "record-messages-and-steps",
          "step-classification"
        ],
        "methods": [
          "data-continuity"
        ]
      }
    },
    "sawtooth-fit": {
      "id": "sawtooth-fit",
      "name": "Sawtooth fit",
      "summary": "Model a clock as a straight drift that is pulled back at correction events, and fit the rate from whole teeth (the correction divided by the time since the previous one).",
      "derived": {
        "studies": [
          "correction-periods",
          "snuadc-stamp-sawtooth"
        ],
        "methods": [
          "stamp-reading"
        ]
      }
    },
    "template-beat-timing": {
      "id": "template-beat-timing",
      "name": "Template beat timing",
      "summary": "Time each beat by fitting a template of the case's own beats, for sub-millisecond precision that does not depend on one fiducial point.",
      "derived": {
        "studies": [
          "airway-heartbeat-pilot",
          "paced-ecg"
        ],
        "methods": [
          "signal-borne-clocks"
        ]
      }
    },
    "unit-mixture": {
      "id": "unit-mixture",
      "name": "Units from drift",
      "summary": "Infer physical units from their drift rates: fit Gaussian mixtures with a shared per-unit width and let a model-selection score choose how many units there are.",
      "derived": {
        "studies": [
          "drift-units",
          "setup-identification"
        ],
        "methods": [
          "stamp-reading"
        ]
      }
    },
    "windowed-cross-correlation": {
      "id": "windowed-cross-correlation",
      "name": "Windowed cross-correlation",
      "summary": "Cross-correlate two signals' envelopes in sliding windows (for example 10 minutes, stepped by 5) to follow an offset that changes over a recording, keeping only windows whose peak is clear.",
      "derived": {
        "studies": [
          "primus-pause-steps",
          "shared-bursts",
          "snuadc-stamp-sawtooth",
          "solar-snuadc-clock",
          "ventilator-breath-matching"
        ],
        "methods": [
          "shared-events"
        ]
      }
    }
  },
  "examples": {
    "bolus-response-latency": {
      "id": "bolus-response-latency",
      "name": "Time from a vasopressor bolus to the pressure response",
      "summary": "How quickly arterial pressure responds to a phenylephrine or ephedrine bolus, with the charted dose times' rounding taken into account. Not feasible in VitalDB, which records bolus totals but no bolus times; kept for a dataset that does.",
      "side": "both",
      "status": "planned",
      "question": "How long after a bolus does arterial pressure start to rise?\n",
      "why": "Charted drug times are rounded, and the rounding is wider than the response latency.\n",
      "variables": [
        "emr-events",
        "snuadc-art"
      ],
      "studies": [
        "drug-effect-latency",
        "emr-rounding",
        "value-bolus-drug-doses"
      ],
      "takeaways": "Planned, but not feasible in this release: the clinical table holds each drug's bolus total per case, and no\nbolus has a time ([[study:value-bolus-drug-doses]]); the event track holds only the case and operation times\n([[observation:event-track-generated]]). Kept as a pointer for a dataset that records bolus times.\n",
      "derived": {}
    },
    "cerebral-autoregulation": {
      "id": "cerebral-autoregulation",
      "name": "A cerebral autoregulation index from arterial pressure and cerebral oxygenation",
      "summary": "A moving correlation between mean arterial pressure and INVOS cerebral oxygenation, and how lag between the two devices changes it.",
      "side": "time",
      "status": "planned",
      "question": "Does cerebral oxygenation follow arterial pressure passively (impaired autoregulation)?\n",
      "why": "The index is a correlation over minutes; a lag between the two signals lowers it. INVOS keeps its own clock,\nwhich the atlas so far bounds only to well within a minute of the recorder ([[study:drug-effect-latency]]).\n",
      "variables": [
        "invos-rso2",
        "snuadc-art"
      ],
      "studies": [
        "drug-effect-latency"
      ],
      "takeaways": "Planned, small: INVOS and an arterial waveform together in about 0.5% of recordings (4 of 846 sampled, about 30\ncases in the release).\n",
      "derived": {}
    },
    "cuff-vs-arterial": {
      "id": "cuff-vs-arterial",
      "name": "Cuff pressure against the arterial line",
      "summary": "12,238 cuff readings against the arterial line at the cuff's own moment, in 2,283 patients: no bias over the cohort at any pressure, but each patient's cuff reads a constant 10 mmHg (SD) above or below their line, and half the arterial readings below 65 mmHg are not below 65 by cuff.",
      "side": "both",
      "status": "draft",
      "question": "When a patient has both, how well does the cuff (non-invasive) mean pressure agree with the arterial line, over a\ncohort and for one patient, and at what moment does a cuff reading apply?\n",
      "why": "A cuff reading takes tens of seconds and is reported once, as a held value; comparing it with the arterial line\nneeds the moment it belongs to. Beyond timing, the cuff's error has two parts: one that is the same for every\nreading in a patient (cuff size, arm, vessel) and one that varies from reading to reading. Only the second\naverages out within a patient; neither shows in a cohort's mean bias.\n",
      "variables": [
        "solar8000-nibp",
        "solar8000-art-numerics"
      ],
      "studies": [
        "numeric-derivation-pilot"
      ],
      "data": "2,457 recordings with cuff readings while the arterial line ran; 12,238 readings with a clean arterial line\naround them (no flush or zeroing within a minute), in 2,283 patients.\n",
      "steps": "1. A new cuff reading is a change of the held NIBP_MBP value, or a value after a gap.\n2. **Its moment.** For each reading, the arterial mean (ART_MBP numeric) over 10 s windows ending −60 to +60 s\n   from the cuff stamp; the lag at which changes between consecutive readings (4,054 pairs, under 10 minutes apart)\n   best follow changes in the arterial mean, which removes each patient's level difference.\n3. **Agreement** at that moment: the cohort's mean difference and its dependence on pressure (binned by the mean\n   of the two, as in a Bland–Altman plot; binning by the arterial value alone manufactures a bias at low\n   pressures by regression to the mean); each patient's mean difference; the spread within a patient.\n4. **Hypotension:** arterial readings below 65 that the cuff also puts below 65; patients with any reading below\n   65 by each.\n",
      "results": "**The cuff value belongs to its stamp.** Changes in the cuff follow changes in the arterial mean best for the\n10 s ending 2 s after the stamp (bootstrap 95% interval: the same), about the arterial numeric's own delay; the\ncorrelation falls from 0.70 there to 0.33 a minute earlier. So the stamp marks the end of the measurement, and\nthe reading can be compared with the arterial line at its stamp.\n\n**Over the cohort, no bias.** Cuff minus arterial is −0.1 mmHg (limits of agreement −27 to +27), and within\n±2 mmHg from 40 to 110 mmHg.\n\n**Per patient, a constant offset.** Each patient's own mean difference ranges from −11 to +13 mmHg (10th to 90th\npercentile; SD 10.2 between patients), as large as the scatter of readings within a patient (SD 9.4). Repeating\ncuff readings averages away the second part, never the first.\n\n**At the threshold.** Of 2,020 arterial readings below 65, the cuff also put 49% below 65; 9% of readings above 65\nby arterial line were below 65 by cuff. Of 874 patients with any arterial reading below 65 at a cuff reading, 395\n(45%) had no cuff reading below 65; over the cohort, 34% of patients had a cuff reading below 65 against 38% by\narterial line.\n\n[[figure:cuff-vs-arterial]]\n",
      "levels": {
        "error": "both",
        "population": "The mean bias is −0.1 mmHg at every pressure; even so, hypotension is found in 34% of patients by cuff against 38% by arterial line.",
        "individual": "Each patient's cuff sits a constant −11 to +13 mmHg from their arterial line, so 45% of patients with arterial readings below 65 have no cuff reading below 65."
      },
      "takeaways": "- Compare a cuff reading with the arterial line at its stamp: the stamp marks the end of the measurement.\n- A cohort's zero bias says nothing about one patient: half the cuff's error is fixed per patient, so repeating\n  cuff readings does not remove it.\n- A cuff threshold misses arterial hypotension in about half of the readings that matter; a study that defines\n  hypotension by cuff counts a different set of patients.\n",
      "limitations": "- The arterial line is not the truth either: its level differs between routes ([[example:hypotension-burden]]),\n  and a damped line would show as cuff-minus-arterial error.\n- Mean pressure only; systolic and diastolic cuff values are known to agree worse.\n- Cuff readings are compared only while the arterial line runs, which selects patients and moments.\n",
      "code": [
        "vitaldb-bench/examples/cuff_vs_arterial.py",
        "vitaldb-bench/examples/run_scan.sh"
      ],
      "reproduce": "```\nbash examples/run_scan.sh cuff_vs_arterial             # 8 parallel parts, about 5 minutes\npython examples/cuff_vs_arterial.py agg <work>/examples/cuff_vs_arterial/part_*.jsonl --out out/cuff\n```\n",
      "figures": [
        {
          "id": "cuff-vs-arterial",
          "file": "examples/cuff-vs-arterial.png",
          "theme": "agreement and residuals",
          "source": "vitaldb-bench examples/cuff_vs_arterial.py",
          "caption": "Cuff against arterial mean pressure. A: how well changes between consecutive cuff readings follow changes in the arterial mean, by where the 10 s arterial window ends against the cuff stamp. B: cuff minus arterial against their mean, with the mean difference per 10 mmHg (orange). C: each patient's mean difference.",
          "alt": "A curve peaking just after zero; a broad cloud around zero with a flat orange line; a wide histogram centred near zero."
        }
      ],
      "derived": {}
    },
    "fluid-balance": {
      "id": "fluid-balance",
      "name": "Intraoperative fluid balance as a distribution",
      "summary": "Fluids in minus fluids out, each a heaped charted volume, as a distribution per case: the recording alone makes a balance uncertain by about ±60 mL, and by ±190 mL when the blood loss was written as a round 500 mL.",
      "side": "value",
      "status": "draft",
      "question": "What was this patient's net fluid balance at the end of the operation, and how precisely do the records say?\n",
      "why": "The balance adds and subtracts four charted volumes, each heaped on its own grid: crystalloid on 100 and 50 mL\n([[study:value-model-intraop-crystalloid]]), blood loss on 50, 100 and 500 mL ([[study:value-model-intraop-ebl]]),\nurine output on 50 and 100 mL ([[study:value-model-intraop-uo]]), colloid on container sizes\n([[study:value-intraop-colloid-containers]]). Their rounding errors add up.\n",
      "variables": [
        "intraop-crystalloid",
        "intraop-colloid",
        "intraop-ebl",
        "intraop-uo"
      ],
      "studies": [
        "value-model-intraop-crystalloid",
        "value-model-intraop-ebl",
        "value-model-intraop-uo",
        "value-intraop-colloid-containers"
      ],
      "data": "The 2,639 cases with crystalloid, blood loss and urine output recorded and no blood products (the release gives\nproducts in units, and the volume of a unit is not recorded). A recorded 0 counts as exactly 0.\n",
      "steps": "1. For each case, draw 4,000 true values of each volume from the pinned vitaldb-resolution library (v0.6.0, model\n   version 2, with each volume conditioned on the case's anaesthesia duration).\n2. Form crystalloid + colloid − blood loss − urine output for each draw.\n3. Summarise each case by its median and a 95% interval, and split the variance between the four terms.\n",
      "results": "**Three cases.** [[case:6156]] is a typical width: 170 to 291 mL. In [[case:4167]] the recorded balance is −100 mL\nbut the interval runs from −268 to +120 mL, so even its sign is uncertain. [[case:5253]] is wide because its blood\nloss was written as 3,000 mL.\n\n[[figure:fb-examples]]\n\n**The cohort.** The median 95% interval is 116 mL wide (78 to 142 mL, 10th to 90th percentile). Crystalloid\ncontributes about 55% of the variance in the median case and blood loss 36%; urine output and colloid little. A\nseparate group of 117 cases is about 380 mL wide: 97 of them have a blood loss recorded as exactly 500 mL, which\nthe model reads as possibly rounded to the nearest 500. In 95 cases the interval spans zero. The medians of the\ndistributions sit within 1 mL of the recorded balance, so the rounding adds spread, not bias.\n\n[[figure:fb-cohort]]\n",
      "levels": {
        "error": "random",
        "population": "The distributions' medians sit within 1 mL of the recorded balance: the rounding adds no bias to a cohort's mean.",
        "individual": "Each balance is uncertain by about ±60 mL, by about ±190 mL with a round 500 mL blood loss; in 95 cases even its sign is unknown."
      },
      "takeaways": "- A computed fluid balance from charted volumes is uncertain by about ±60 mL from the recording alone, before any\n  error in estimating blood loss itself.\n- Round blood losses (500, 1,000 mL) dominate the uncertainty where they occur.\n",
      "limitations": "- Measurement error beyond rounding (estimating blood loss by eye, for one) is not included; it is larger.\n- Cases with blood products are left out.\n",
      "open_questions": [
        {
          "title": "Blood products in millilitres",
          "kind": "assumption",
          "summary": "The release gives red cells and plasma in units. A unit's volume (typically 250–350 mL for red cells) is not recorded, so the balance leaves these cases out; an assumed range would bring them in with a wider distribution."
        }
      ],
      "code": [
        "vitaldb-bench/examples/fluid_balance.py"
      ],
      "reproduce": "```\npython examples/fluid_balance.py --resolution <vitaldb-resolution at the pinned commit> \\\n    --root <VitalDB release> --out out/fb --figure 6156 4167 5253\n```\n",
      "figures": [
        {
          "id": "fb-examples",
          "file": "examples/fb-examples.png",
          "theme": "distributions of measured parameters",
          "source": "vitaldb-bench examples/fluid_balance.py",
          "records": [
            6156,
            4167,
            5253
          ],
          "caption": "Fluid balance as a distribution (blue) against the recorded numbers' balance (dashed) in [[case:4167]], [[case:5253]] and [[case:6156]].",
          "alt": "Three histograms of fluid balance; the first straddles zero, the second is centred near minus 900 mL with long tails, the third is between 150 and 300 mL."
        },
        {
          "id": "fb-cohort",
          "file": "examples/fb-cohort.png",
          "theme": "distributions of measured parameters",
          "source": "vitaldb-bench examples/fluid_balance.py",
          "caption": "2,639 cases without blood products. Left: 95% interval width against the recorded balance. Right: the widths; the group near 380 mL has a blood loss written as a round 500 or 1,000 mL.",
          "alt": "A scatter with most points between 50 and 200 mL wide and a separate band near 380 mL, and a histogram with a main peak near 120 mL and a small one near 380 mL."
        }
      ],
      "derived": {}
    },
    "gas-gradients-at-the-draw": {
      "id": "gas-gradients-at-the-draw",
      "name": "End-tidal to arterial CO₂, and pulse oximetry against blood gas, at the right moment",
      "summary": "For 4,281 blood gases, reading the monitor when the blood was drawn rather than at the lab stamp makes changes in end-tidal CO₂ track changes in arterial CO₂ far better (within-patient correlation 0.74 against 0.54), and agreement peaks exactly at the estimated draw. Oximetry shows the same timing (agreement peaks at the draw), but the 100% ceiling hides the size of the oximetry gradient for three gases in four.",
      "side": "time",
      "status": "draft",
      "question": "How far apart are end-tidal and arterial CO₂, and SpO₂ and SaO₂, when they are compared at the same moment, and\nhow precisely does the release say?\n",
      "why": "A blood-gas result's timestamp is 3–12 minutes after the draw ([[observation:lab-time-is-specimen]]). Comparing it\nwith the monitor value at the timestamp compares two different moments; during a change in ventilation that adds\nspread, and sometimes bias, to the gradient. On the value side, every term is a whole number, and both SpO₂ and\nSaO₂ stop at 100%. The SpO₂–SaO₂ comparison follows [[reference:eytan2019revisiting]], which compared bedside\noximetry with arterial blood gases in critically ill children.\n",
      "variables": [
        "pco2",
        "sao2",
        "lab-blood-gas",
        "primus-numerics",
        "solar8000-spo2",
        "snuadc-art"
      ],
      "studies": [
        "lab-timestamp-meaning",
        "arterial-line-accesses",
        "spo2-latency"
      ],
      "observations": [
        "lab-time-is-specimen",
        "numerics-not-front-stamped"
      ],
      "data": "Blood gases matched to exactly one arterial-line access in the 15 minutes before their lab stamp\n(`lab_draw:single` in [[study:arterial-line-accesses]]): 4,281 gases in 1,349 cases, each with Primus end-tidal\nCO₂ and Solar8000 SpO₂ in the recording. Gases with several candidate accesses (459) are left out.\n",
      "steps": "1. Take the start of the matched arterial-line access as the draw. The monitor value is the median of its readings\n   over ±60 s of that moment.\n2. Draw 400 Monte Carlo samples per gas of:\n    - the draw time, uniform over the access (median 42 s long), plus the monitor's stamping delay (a numeric\n      describes a window that ended 1–9 s before its stamp; [[observation:numerics-not-front-stamped]]);\n    - each monitor reading's true value, uniform over ±0.5 of the whole number written; an SpO₂ of 100 is\n      censored, so its true value is anywhere from 99.5 to 100;\n    - the lab value, from the pinned vitaldb-resolution library (v0.6.0), with SaO₂ capped at 100.\n3. Summarise each gradient by its median and 95% interval. Repeat with the draw time fixed (rounding only), and\n   with the single reading nearest the draw instead of the ±60 s median.\n4. Compare the point gradient with the monitor read at the draw and at the lab stamp.\n5. **Test the timing within patients.** Between consecutive gases of the same patient, correlate the change in\n   PaCO₂ with the change in EtCO₂, read at the draw and at the lab stamp (95% intervals by resampling patients).\n   Between-patient differences in the gradient (set by each patient's dead space) cancel in a change, so this\n   tests timing, not physiology.\n6. **Timing scan.** Repeat step 5 with the monitor read at every whole minute from 10 minutes before the start of\n   the access to 15 minutes after it. If the draw estimate is right, agreement should peak at 0, not at the lab\n   stamp. For oximetry, gases are chosen by the lab value (SaO₂ ≤ 98%, below the analyser's ceiling), never by the\n   monitor reading, so no offset is favoured, and a rank correlation is used, which treats SpO₂ = 100 as a tie.\n",
      "results": "**Two gases.** In [[case:136]] end-tidal CO₂ falls sharply about 2½ minutes after the draw, just before the lab\nstamp. Read at the draw, PaCO₂ − EtCO₂ is 6.0 mmHg (95% interval 5.2–6.8); read at the stamp it would be 11. In\n[[case:16]] the CO₂ gradient is the same either way (11 mmHg), but SpO₂ is 100 at the draw: with SaO₂ at 99, the\noximetry gradient runs from −1.2 to +0.7, and its sign is not known.\n\n[[figure:bg-examples]]\n\n**Does the draw time matter? Yes, within patients.** Between consecutive gases of the same patient (2,911 pairs),\nthe change in PaCO₂ follows the change in EtCO₂ with a correlation of 0.74 (95% interval 0.69–0.78) when the\nmonitor is read at the estimated draw, against 0.54 (0.47–0.61) at the lab stamp. Read minutes late, the monitor\nmisses what happened between the draw and the stamp.\n\n[[figure:bg-changes]]\n\n**Agreement peaks at the estimated draw.** Read at each minute from 10 minutes before the access to 15 minutes\nafter, the within-patient correlation is highest at 0 (0.73) and falls off on both sides; at the median lab stamp\n(7.2 minutes) it is 0.56. This is independent support for the draw estimate: nothing in the method forces the peak\nto land at the access.\n\n[[figure:bg-lagscan]]\n\n**Oximetry shows the same timing.** In gases with SaO₂ ≤ 98% (881 gases, 379 within-patient pairs), the rank\ncorrelation of changes in SaO₂ and SpO₂ peaks at the estimated draw (0.69) and falls to 0.34 at the lab stamp; the\n95% intervals at the two moments do not overlap. Over all gases, with SpO₂ = 100 as ties, the peak is also at the\ndraw (0.51, against 0.32 at the stamp).\n\n[[figure:bg-lagscan-spo2]]\n\n**PaCO₂ − EtCO₂ across patients.** Median 7 mmHg (interquartile range 5–10; 5th–95th percentile 1–16). Pooled over\npatients the two agree only moderately (correlation 0.61 at the draw, 0.50 at the stamp), because each patient has\ntheir own gradient: the patients' typical gradients spread with an SD of about 4.1 mmHg, while one patient's\ngradient varies by about 1.5 mmHg. Pooled, reading at the stamp instead of the draw barely changes the\ndistribution, though it moves an individual gas's gradient by 3 mmHg or more in 11.5% of gases.\n\n**How precisely each gradient is known.** Per gas, the median 95% interval is 1.57 mmHg wide; rounding alone gives\n1.54, so the uncertain draw time adds almost nothing once the monitor is averaged over ±60 s. Using the single\nnearest reading instead widens it to 2.29 mmHg (up to 4.3 at the 95th percentile). This is the precision of the\nrecording, not agreement between the two measurements.\n\n**SaO₂ − SpO₂.** Median 0 (interquartile range −0.8 to 0). SpO₂ is 100 at the draw for 77% of gases and SaO₂ is\n100 for 47%. With SpO₂ = 100 read as \"99.5 to 100\", the interval includes zero for half of all gases: the release\ncan say that the two agree to within a percentage point, but not which way they differ. Where SpO₂ is below 100\n(about 980 gases) the sign is mostly resolved: SpO₂ is clearly above SaO₂ in 45% of them and clearly below in 26%.\n\n[[figure:bg-cohort]]\n\n**Each gas as a point.** Pooled over patients, arterial CO₂ follows end-tidal CO₂ only moderately (correlation\n0.61), sitting about 7 mmHg above it; the spread is mostly between patients (see above). For oxygen, three gases in four are in the column at SpO₂ = 100, where the oximeter cannot say\nhow high the saturation really is; SaO₂ in that column runs from about 95 to 100. Where SpO₂ is below 100, SaO₂\nstill reads 100 in 17% of gases, a pattern that would fit an analyser that calculates SaO₂ from PO₂ (see the open\nquestions).\n\n[[figure:bg-scatter]]\n",
      "levels": {
        "error": "systematic",
        "population": "Pooled over patients, reading the monitor at the lab stamp instead of the draw changes the gradient's distribution little.",
        "individual": "Within a patient, the stamp is always about 7 minutes late, a systematic shift: the monitor tracks the blood gas far worse there (CO₂ change correlation 0.54 against 0.74)."
      },
      "takeaways": "- Pair a blood gas with the monitor at the estimated draw, not at the lab stamp. Pooled over patients the\n  difference is small, but within a patient the monitor tracks the blood gas much better at the draw (CO₂: 0.74\n  against 0.54; oximetry below the ceiling: 0.69 against 0.34), and agreement peaks there for both.\n- Expect wide scatter between patients: PaCO₂ − EtCO₂ is a property of each patient (dead space), so one\n  patient's gradient says little about another's.\n- Average the monitor over a minute or so around the draw; the single nearest reading adds more uncertainty than\n  the draw time does.\n- SaO₂ − SpO₂ at high saturation is set by the 100% ceiling and the 1% grid, not by physiology; treat SpO₂ = 100\n  as censored.\n",
      "limitations": "- The match between a blood gas and an arterial-line access is evidence, not proof: 77% of gases had an access in\n  the window against 26% of random times. A wrong match is not part of the per-gas interval.\n- The draw is taken to happen during the access; a sample drawn after the line was flushed and closed again\n  would be missed.\n- The lab value's analyser error and the oximeter's stated accuracy (a few percentage points) are not included;\n  both are larger than the rounding.\n- The vitaldb-resolution library gives a recorded SaO₂ of 100 an upper bound of 100.475; the example caps it at\n  100.\n",
      "open_questions": [
        {
          "title": "SaO₂ above 100 in the library",
          "kind": "next step",
          "summary": "vitaldb-resolution treats a recorded SaO₂ of 100 like any other whole number, with the true value up to 100.475. A cap at 100 (a top-coded value, like the other reporting limits) would fix this at the source."
        },
        {
          "title": "Is SaO₂ measured or calculated?",
          "kind": "assumption",
          "summary": "Many blood-gas analysers calculate SaO₂ from PO2 and pH rather than measuring it by co-oximetry. Which was used decides whether SaO₂ − SpO₂ compares two measurements or a measurement with a model."
        }
      ],
      "code": [
        "vitaldb-bench/examples/blood_gas_gradients.py"
      ],
      "reproduce": "```\npython examples/blood_gas_gradients.py run --resolution <vitaldb-resolution at the pinned commit> \\\n    --root <VitalDB release> --draws-csv lab_draw_labels.csv --out out/bg --cases 0:130   # repeat over chunks\npython examples/blood_gas_gradients.py summarise --out out/bg --root <VitalDB release> \\\n    --draws-csv lab_draw_labels.csv --figure 136-1 16-3\npython examples/blood_gas_gradients.py lagscan --root <VitalDB release> --draws-csv lab_draw_labels.csv \\\n    --out out/bg --cases 0:140   # repeat over chunks: the monitor at every minute around the draw\npython examples/blood_gas_gradients.py lagsummary --out out/bg   # change figure and timing scans\n```\n",
      "figures": [
        {
          "id": "bg-examples",
          "file": "examples/bg-examples.png",
          "theme": "example traces",
          "source": "vitaldb-bench examples/blood_gas_gradients.py",
          "records": [
            136,
            16
          ],
          "caption": "End-tidal CO₂ (top) and SpO₂ (bottom) around a blood draw in [[case:136]] and [[case:16]]: the arterial-line access (orange), the lab stamp (dashed) and the lab value at the draw (red). Titles give the gradient's median and 95% interval.",
          "alt": "Four time-series panels. In the left column end-tidal CO₂ drops sharply just before the lab stamp; in the right column SpO₂ sits at 100 at the draw."
        },
        {
          "id": "bg-cohort",
          "file": "examples/bg-cohort.png",
          "theme": "agreement and residuals",
          "source": "vitaldb-bench examples/blood_gas_gradients.py",
          "caption": "4,281 blood gases. Left: PaCO₂ − EtCO₂ with the monitor read at the estimated draw and at the lab stamp. Middle: per-gas 95% interval width from rounding only, with the draw time added, and with the nearest single reading. Right: SaO₂ − SpO₂ per gas, ranked, with SpO₂ = 100 censored (red).",
          "alt": "A pair of overlapping histograms centred near 7 mmHg; histograms of interval widths peaking near 1.5 and 2.3 mmHg; a ranked plot of oximetry gradients in steps of one percentage point."
        },
        {
          "id": "bg-scatter",
          "file": "examples/bg-scatter.png",
          "theme": "agreement and residuals",
          "source": "vitaldb-bench examples/blood_gas_gradients.py",
          "caption": "One point per blood gas, the monitor read at the estimated draw. Every value is a whole number, so each point is drawn at a random place within its rounding interval (a value of 100 within 99.5–100). Left: PaCO₂ against EtCO₂, both axes from zero, with the line of equality and the line 7 mmHg above it. Right: SaO₂ against SpO₂, axes from 80%; red points have SpO₂ = 100.",
          "alt": "Left, a cloud of points between 25 and 55 mmHg lying just above the line of equality. Right, most points in a vertical red column at SpO2 100 with SaO2 from 95 to 100, and a sparser blue cloud below."
        },
        {
          "id": "bg-changes",
          "file": "examples/bg-changes.png",
          "theme": "agreement and residuals",
          "source": "vitaldb-bench examples/blood_gas_gradients.py",
          "caption": "Change in PaCO₂ against change in EtCO₂ between consecutive blood gases of the same patient (2,911 pairs), with the monitor read at the estimated draw (left) and at the lab stamp (right). Each value is a whole number, so points are drawn at a random place within their rounding interval; the dashed line is equal change.",
          "alt": "Two scatter plots of changes; the left cloud lies tightly along the line of equal change, the right one is rounder and more spread."
        },
        {
          "id": "bg-lagscan",
          "file": "examples/bg-lagscan.png",
          "theme": "delay and window fits",
          "source": "vitaldb-bench examples/blood_gas_gradients.py",
          "caption": "Agreement between PaCO₂ and EtCO₂ with the monitor read at each minute from 10 minutes before the start of the matched arterial-line access to 15 minutes after it: within patients (change against change, blue, with 95% interval) and pooled over all gases (grey). Both peak at the estimated draw (0); the lab stamp falls at a median 7.2 minutes.",
          "alt": "Two curves rising to a peak at zero minutes and falling on both sides; the within-patient curve peaks at 0.73."
        },
        {
          "id": "bg-lagscan-spo2",
          "file": "examples/bg-lagscan-spo2.png",
          "theme": "delay and window fits",
          "source": "vitaldb-bench examples/blood_gas_gradients.py",
          "caption": "Within-patient rank correlation of changes in SaO₂ and SpO₂ with the oximeter read at each minute from 10 minutes before the start of the access to 15 minutes after: gases with SaO₂ ≤ 98% (blue, 95% interval) and all gases (grey). Gases are chosen by the lab value, not the monitor, so no moment is favoured. Both peak at the estimated draw.",
          "alt": "Two curves peaking at zero minutes; the blue one reaches about 0.69 and falls to about 0.3 by the lab stamp at 7.2 minutes."
        }
      ],
      "references": [
        "eytan2019revisiting"
      ],
      "derived": {}
    },
    "hypotension-burden": {
      "id": "hypotension-burden",
      "name": "Minutes of hypotension per patient, from the monitor and from the waveform",
      "summary": "Minutes with MAP below 65 mmHg in 3,476 recordings, from the monitor's numeric and from the arterial waveform's beats: the two routes to one pressure differ by several mmHg per patient, which moves half of all patients' minutes by 5 or more and a fifth of their labels, and the numeric's averaging hides short dips in every patient.",
      "side": "both",
      "status": "draft",
      "question": "How many minutes was each patient's mean arterial pressure below 65 mmHg, and does it matter whether the minutes\ncome from the monitor's numeric or from the waveform?\n",
      "why": "Hypotension burden is usually computed from the monitor's mean pressure numeric (Solar8000 ART_MBP): an integer\nevery 2 s, an exponential average over beats, about 2 s late ([[study:numeric-derivation-pilot]],\n[[parameter:art-mbp-delay]]). The same pressure reaches the recorder as the SNUADC waveform, from the monitor's\nanalogue output, where each beat's mean can be computed. The two differ in level (scale, not timing: pressure\nnumerics sit a few mmHg from the analogue waveform), in averaging, and in rounding.\n",
      "variables": [
        "solar8000-art-numerics",
        "snuadc-art"
      ],
      "studies": [
        "numeric-derivation-pilot",
        "numeric-delay-variability"
      ],
      "data": "Every recording with both the ART_MBP numeric and the SNUADC arterial waveform and at least 30 minutes of common,\nplausible data: 3,476 recordings, 11,434 hours.\n",
      "steps": "1. Beat means from the waveform (pressure averaged from one diastolic foot to the next); the numeric moved 1.7 s\n   earlier for its delay.\n2. A 30 s plausibility screen on both (mean 20–200 mmHg, pulse pressure 10–150 mmHg, no flat line), and only the\n   time both are plausible, so that flushes and zeroing are left out of both.\n3. Per patient: minutes below 65, area below 65, dips shorter and longer than 1 minute, and two labels (any minute\n   continuously below 65; 10 minutes or more in total), from the numeric (< 65 and ≤ 65, since it is an integer)\n   and from the waveform, as recorded and moved onto the numeric's level (the median difference in that\n   recording), which separates level from dynamics.\n",
      "results": "**The two routes disagree in level, patient by patient.** The numeric minus the waveform's beat mean is a median\n−1.2 mmHg, but −5.7 to +5.8 mmHg between the 10th and 90th percentile of patients: a constant offset within a\nrecording, from the analogue chain.\n\n**Per patient, that decides the minutes.**\n\n| | Numeric | Waveform | Waveform on the numeric's level |\n|---|---|---|---|\n| Minutes below 65, median per patient | 7.2 | 8.3 | 9.3 |\n| Minutes below 65, mean | 18.2 | 23.2 | 20.9 |\n| 10 minutes or more below 65 | 41.9% | 46.6% | 48.2% |\n| Dips under 1 minute, per recording | 29 | 216 | 221 |\n\nHalf the patients (49%) differ by 5 minutes or more between numeric and waveform (median 4.7 minutes), and the\n\"10 minutes below 65\" label differs for 19% of them. Over the cohort the waveform gives 5.0 more minutes on\naverage (95% interval 4.3 to 5.7): the level offsets do not cancel, because a patient whose waveform sits lower\ngains far more minutes than one whose waveform sits higher loses.\n\n**With the level removed, the averaging is left.** On the numeric's level the waveform still gives more time\nbelow 65 in almost every patient (median 1.3 minutes more; mean 2.7): the numeric's average smooths away the\nshort dips, 29 per recording against 216. The label then differs for 7% of patients.\n\n**The definition matters too.** The numeric is an integer, so ≤ 65 counts every reading of exactly 65: a median\n1.6 more minutes per patient (7.8 at the 90th percentile), and the 10-minute label changes for 6%.\n\n[[figure:hypotension-burden]]\n",
      "levels": {
        "error": "both",
        "population": "The cohort mean moves by 5 minutes between the two routes, and by 2.7 minutes from the averaging alone: neither cancels.",
        "individual": "A level offset of up to ±6 mmHg per patient moves half of all patients' minutes by 5 or more and a fifth of their 10-minute labels."
      },
      "takeaways": "- State which pressure a hypotension burden comes from (monitor numeric or waveform) and how \"below 65\" treats\n  integer values; these choices move a patient's minutes more than many interventions do.\n- The level offset between the monitor and its analogue output is a property of each recording: calibrate one\n  against the other per recording before combining them.\n- Short dips exist only in the waveform; any definition that counts brief hypotension depends on the source.\n",
      "limitations": "- Neither route is the truth: the level offset says the two disagree, not which is right. The monitor's own\n  numeric is calibrated by its transducer zero; the analogue output passes through one more conversion.\n- The plausibility screen removes obvious artefacts only; damped or over-resonant lines are not detected.\n- Area and minutes are counted over the whole recording, not only the anaesthetic.\n",
      "code": [
        "vitaldb-bench/examples/hypotension_burden.py",
        "vitaldb-bench/examples/run_scan.sh"
      ],
      "reproduce": "```\nbash examples/run_scan.sh hypotension_burden          # 8 parallel parts, about 15 minutes\npython examples/hypotension_burden.py agg <work>/examples/hypotension_burden/part_*.jsonl --out out/hypotension\n```\n",
      "figures": [
        {
          "id": "hypotension-burden",
          "file": "examples/hypotension-burden.png",
          "theme": "agreement and residuals",
          "source": "vitaldb-bench examples/hypotension_burden.py",
          "caption": "Minutes with MAP below 65 mmHg in 3,476 recordings. A: per patient, from the waveform's beats and from the monitor's numeric (dotted: 10 minutes). B: dips shorter and longer than a minute per recording. C: numeric minus waveform, as recorded (blue) and with the waveform moved onto the numeric's level (orange), which leaves the averaging.",
          "alt": "A scatter spreading widely around the diagonal, a bar chart with 216 short dips for the waveform against 29 for the numeric, and two histograms, a wide blue one and a narrow orange one just left of zero."
        }
      ],
      "derived": {}
    },
    "induction-hypotension": {
      "id": "induction-hypotension",
      "name": "Post-induction hypotension, from the charted start and from the devices",
      "summary": "The charted anaesthesia start comes a median 14 minutes before the patient is induced; measured from it, \"hypotension within 20 minutes of induction\" is found in 27% of 2,913 patients, against 51% from the devices' induction, and the label changes for a quarter of patients.",
      "side": "time",
      "status": "draft",
      "question": "How many patients have a mean arterial pressure below 65 mmHg in the first 20 minutes after induction, and does it\nmatter which time is taken as induction?\n",
      "why": "Post-induction hypotension is a common outcome, usually counted from the charted anaesthesia start. Anaesthesia\nstart entered on a 5-minute mark is rounded up ([[study:emr-rounding]]), about 2½ minutes late; but before that\nis the question of what the charted time stands for. The devices mark induction themselves: the BIS falls below\n60 as the patient loses consciousness, and the propofol pump starts.\n",
      "variables": [
        "emr-events",
        "bis-index",
        "pump-rates",
        "solar8000-nibp",
        "solar8000-art-numerics"
      ],
      "studies": [
        "emr-rounding"
      ],
      "data": "2,913 cases with a device induction within −20 to +40 minutes of the charted start (88% from the BIS, the rest\nfrom the propofol pump), a recovered case clock, and blood pressure monitored in both windows (at least two cuff\nreadings, or an arterial line running from the window's start); half have an arterial line.\n",
      "steps": "1. Device induction ([[study:emr-rounding]], `tools/emr_markers.py`): BIS below 60 held 30 s after BIS of 80 or\n   more, else the first propofol pump rate.\n2. Low pressure: a cuff reading below 65, or the arterial numeric below 65 for a minute or more.\n3. Hypotension within 20 minutes from the charted start, and from the device induction; cases split into\n   5-minute and minute entries by the recovered case clock.\n4. A sweep of the 20-minute window's start from 40 minutes before to 10 minutes after the device induction.\n",
      "results": "**The charted start is not induction.** The device induction comes a median 14 minutes after the charted start\n(10th–90th percentile 6 to 25 minutes), in 5-minute entries and minute entries alike. The 2½-minute rounding is\nsmall next to this: the charted time marks the start of anaesthesia care, not the induction.\n\n**The cohort's incidence nearly halves.**\n\n| Window from | All (2,913) | 5-minute entries (1,613) | Minute entries (1,300) |\n|---|---|---|---|\n| Charted start | 27.1% | 26.5% | 27.8% |\n| Device induction | 51.1% | 49.7% | 52.8% |\n| Difference (95% interval) | +24.0 points (22.4 to 25.6) | +23.3 | +25.0 |\n\nMoving the window's start shows why: the incidence peaks when the window opens at induction, and falls steeply as\nit opens earlier and covers the minutes before induction, when pressure is rarely low.\n\n**Per patient, a quarter of labels change** (26%): 734 patients hypotensive from the device induction are not from\nthe charted start, and 34 the other way. Where both windows find a low reading, it comes a median 4.1 minutes\nafter the device induction, and 13.8 minutes after the charted start.\n\n[[figure:induction-hypotension]]\n",
      "levels": {
        "error": "systematic",
        "population": "The incidence is 27% from the charted start and 51% from the device induction: the shift goes the same way in every patient, so it does not cancel.",
        "individual": "26% of patients change label, nearly all from \"no hypotension\" to \"hypotension\"."
      },
      "takeaways": "- Do not take the charted anaesthesia start as induction in VitalDB: it comes a median 14 minutes before it. Find\n  induction from the devices (BIS, the propofol pump, the start of ventilation).\n- A systematic shift in an event time moves a cohort's estimate as much as each patient's label: unlike rounding,\n  it does not average out.\n- Report which time a \"post-induction\" window starts from.\n",
      "limitations": "- Device induction is BIS loss of consciousness in most cases; it comes after the induction drugs are given, so\n  the true induction is a little earlier still. Cases without a BIS or a pump are left out.\n- Cuff readings are sparse before the arterial line is placed; a short dip between readings is missed in both\n  windows alike.\n- Monitoring had to cover both windows, which selects cases.\n",
      "code": [
        "vitaldb-bench/examples/induction_hypotension.py",
        "vitaldb-bench/tools/emr_markers.py",
        "vitaldb-bench/examples/run_scan.sh"
      ],
      "reproduce": "```\nbash examples/run_scan.sh induction_hypotension        # 8 parallel parts, about 10 minutes\npython examples/induction_hypotension.py agg <work>/examples/induction_hypotension/part_*.jsonl --root <VitalDB release> --out out/induction\n```\n",
      "figures": [
        {
          "id": "induction-hypotension",
          "file": "examples/induction-hypotension.png",
          "theme": "delay and window fits",
          "source": "vitaldb-bench examples/induction_hypotension.py",
          "caption": "Post-induction hypotension. A: where the charted anaesthesia start sits against the device induction, for 5-minute and minute entries. B: the incidence of MAP below 65 within a 20-minute window, by where the window opens against the device induction (dotted: the median charted start). C: the incidence from the charted start and from the device induction, and the share of patients whose label changes.",
          "alt": "Two overlapping histograms centred about 14 minutes before zero; a curve rising from about 7% to a peak of 51% at zero; paired bars, grey near 27% and blue near 51%."
        }
      ],
      "derived": {}
    },
    "oliguria-labels": {
      "id": "oliguria-labels",
      "name": "Oliguria labels for a prediction model",
      "summary": "One label per patient, oliguric or not, from three rounded numbers: 10% of labels are uncertain and 2.6% are expected to be wrong, which sets a floor under the error any model can be measured to have. A typical model is barely affected; a good one is.",
      "side": "value",
      "status": "draft",
      "question": "If a prediction model is trained and scored on \"oliguric during the operation: yes or no\", how good are those\nlabels, and what do they do to the model's measured performance?\n",
      "why": "A model is trained and scored one patient at a time, so it meets each label's rounding unaveraged. The oliguria\nlabel is a threshold (0.5 mL/kg/h) on a ratio of three rounded numbers ([[example:urine-output-per-kg-per-hour]]):\nwhere the recorded numbers fall near the threshold, the true rate may lie on the other side. Across a cohort these\nerrors cancel ([[example:regression-with-rounding]]); in a per-patient label they do not.\n",
      "variables": [
        "intraop-uo",
        "weight",
        "ane-duration"
      ],
      "studies": [
        "value-model-intraop-uo",
        "value-model-weight",
        "value-model-anestart",
        "value-model-aneend",
        "timestamp-derived-intervals"
      ],
      "data": "The 3,645 cases with a positive urine output, a weight and both anaesthesia times; 1,276 (35%) are oliguric by\nthe recorded numbers.\n",
      "steps": "1. For each case, draw 200 true rates from the vitaldb-resolution library (v0.6.0) and take P(oliguric), the\n   share of draws below 0.5 mL/kg/h. A label is uncertain when 0.05 < P < 0.95; the expected number of wrong\n   recorded labels is the sum over cases of P(the true label differs from the recorded one).\n2. **The floor.** Use the true rate itself as a \"model\", with increasing noise added on the log scale, and score\n   it both against the true labels (from the same draw) and against the recorded labels.\n3. **A real model.** A logistic regression on preoperative and case-type variables (age, height, BMI, ASA,\n   emergency, laboratory values, department, approach, anaesthesia and operation type), 5-fold cross-validated,\n   trained on the recorded labels (hard) or on P(oliguric) (soft labels), and scored against both kinds of label.\n",
      "results": "**The labels.** 370 of the 3,645 labels (10%) are uncertain, and 96 (2.6%) are expected to be wrong. They are\nconcentrated in cases whose urine output was written as a round 50 or 100 mL (200 mL or less): 30% uncertain,\nagainst 3% of other cases.\n\n**A floor under the error.** A model that knew each patient's true rate exactly would be \"wrong\" on 2.6% of\nrecorded labels (AUC 0.996 rather than 1). Below an error rate of about 10% against the true labels, the\nrecorded labels add a visible share of the measured error: a model 98.3% right is scored 96.7%, one 96.4% right is\nscored 95.3%. Above that the labels' own error is lost in the model's.\n\n[[figure:oliguria-labels]]\n\n**A typical model is barely affected.** The preoperative model reaches AUC 0.658 against the recorded labels and\n0.659 against the true labels (0.653 to 0.664 over draws). The spread from the labels (SD 0.003) is a third of the\nspread from which patients happen to be in the sample (bootstrap SD 0.010). Training on soft labels changes the\nAUC by less than 0.001. Dropping the uncertain cases from the test set, a common way of \"cleaning\" labels, raises\nthe AUC to 0.678: not because the labels are better, but because the cases near the threshold, which are hard\nfor any model, are gone.\n",
      "levels": {
        "error": "random",
        "population": "A typical model's AUC moves by less than 0.001; the labels add a third of the sampling noise.",
        "individual": "10% of labels are uncertain and 2.6% are expected wrong, a floor under the error any model can be measured to have."
      },
      "takeaways": "- Report how many labels are uncertain, and P(label) for each case, alongside a labelled dataset.\n- The labels set a floor under the measurable error. It matters for good models, and for comparing models that\n  differ by a few points; it does not matter for a model as weak as this one.\n- Do not drop uncertain cases from a test set: it flatters the model. Score against the draws instead, or weight\n  each case by P.\n",
      "limitations": "- The inputs are drawn independently, and the library models only rounding; any systematic error in charted urine\n  output (a bag emptied late, a missed volume) is not included and would not cancel.\n- The preoperative model is a stand-in for any model; better models feel the floor more.\n",
      "code": [
        "vitaldb-bench/examples/oliguria_labels.py"
      ],
      "reproduce": "```\npython examples/oliguria_labels.py --resolution <vitaldb-resolution at the pinned commit> \\\n    --root <VitalDB release> --out out/oliguria --draws 200\n```\nUnder a minute on a laptop.\n",
      "figures": [
        {
          "id": "oliguria-labels",
          "file": "examples/oliguria-labels.png",
          "theme": "distributions of measured parameters",
          "source": "vitaldb-bench examples/oliguria_labels.py",
          "caption": "A: each case's probability of being oliguric, for the cases where it is neither 0 nor 1, coloured by the label the recorded numbers give. B: the error rate of models of the true rate with increasing noise, scored against the true labels and against the recorded labels; a perfect model is \"wrong\" on 2.6% of recorded labels.",
          "alt": "Left, two overlapping histograms, blue piled near zero and orange near one, overlapping between 0.15 and 0.5; right, a line starting at 2.6% above the diagonal and joining it by about 15%."
        }
      ],
      "derived": {}
    },
    "pat-vs-bp": {
      "id": "pat-vs-bp",
      "name": "Pulse arrival time against blood pressure, before and after synchronisation",
      "summary": "In 3,270 recordings, PAT tracks systolic pressure only once each pulse is paired with its own beat: the hourly correlation moves from a mean of −0.04 to −0.21 (88% of recordings negative), reproducing Ruffolo et al. 2025 in a second dataset; paired naively, PAT is a heart-rate signal.",
      "side": "time",
      "status": "draft",
      "question": "Does pulse arrival time (PAT) follow blood pressure within a patient, and how much of the answer depends on\nsynchronising the ECG and the finger pulse correctly?\n",
      "why": "PAT is studied as a cuffless blood-pressure surrogate, with mixed results. In VitalDB the stored ECG-to-PLETH delay\nis about 0.7 s ([[study:pat-offset-census]]), longer than one RR interval at faster heart rates, so pairing each\npulse with the next R peak puts it one beat out; and the PLETH reaches the recorder on the oximeter's 30 Hz frames,\nwith a sawtooth of 33 ms slips ([[study:ppg-sawtooth]]). Ruffolo et al. found in a paediatric ICU that the expected\nnegative PAT–SBP correlation only appears after correcting such errors ([[reference:ruffolo2025high]], appendix\nB), and suspected naive synchronisation makes PAT follow heart rate instead. This example tests both in VitalDB.\n",
      "variables": [
        "snuadc-ecg-ii",
        "snuadc-pleth",
        "snuadc-art",
        "solar8000-art-numerics"
      ],
      "studies": [
        "pat-offset-census",
        "ppg-sawtooth",
        "ppg-offset-reference",
        "ppg-art-reference"
      ],
      "observations": [
        "fiducial-quantisation"
      ],
      "data": "Every recording with SNUADC ECG II, PLETH and a pulsatile arterial line at 500 Hz, and some variation in systolic\npressure (SD of 3 mmHg or more): 3,270 recordings, 60 minutes each from 10 minutes into the overlap (as\n[[example:pulse-arrival-time]]). All three signals are on one clock, the SNUADC's.\n",
      "steps": "1. R peaks in ECG II; per beat, systolic pressure (the arterial peak after its upstroke), mean pressure (foot to\n   foot) and heart rate (the RR interval ending at the beat).\n2. PAT, R peak to the steepest PLETH upstroke, under five timings:\n   **naive**, each R peak paired with the first upstroke 0.1 s or more after it;\n   **own beat**, the upstroke within 0.25 s of the recording's typical delay;\n   **+ sawtooth removed**, step 1 of [[example:pulse-arrival-time]];\n   **+ global delay**, step 2, a constant, so every correlation must equal the previous one (a built-in check);\n   and **arterial PAT**, R peak to the arterial upstroke, with neither problem, as the reference.\n3. Pearson r per recording between PAT and SBP, MAP and heart rate, and between PAT and SBP with heart rate held\n   constant (partial r): beat to beat, and over 30 s windows (window medians).\n4. As Ruffolo et al. appendix B: the mean of the beat-paired, hour-long r and the share of recordings with r below\n   0.\n",
      "results": "**The appendix B result, reproduced.**\n\n| PAT against SBP, r over one hour per patient | Ruffolo et al. 2025 (paediatric ICU, 3,316 patients) | VitalDB (adult surgery, 3,270 recordings) |\n|---|---|---|\n| Naive synchronisation | Centred on 0; about 50% below 0 | Mean −0.04; 60% below 0 |\n| Corrected | Mean −0.16; 84% below 0 | Mean −0.21; 88% below 0 |\n\n[[figure:pat-vs-bp-ruffolo]]\n\n**Paired naively, PAT is a heart-rate signal.** Beat to beat, naive PAT correlates with heart rate (median r\n−0.40) and hardly with SBP (−0.04); with heart rate held constant, nothing is left (−0.00). Paired with its own\nbeat, PAT does not follow heart rate (0.00) and does follow SBP (−0.19; −0.18 with heart rate held constant). In\nthe 523 recordings where naive pairing is one beat out more than a fifth of the time, the same holds more strongly\n(PAT–HR −0.36 naive, 0.01 paired). This confirms Ruffolo et al.'s suspicion that naive synchronisation makes PAT\ntrack heart rate.\n\n**Over 30 s windows the link is stronger,** and the corrected finger PAT matches the arterial PAT:\n\n| Median r per recording, 30 s windows | PAT–SBP | PAT–HR | PAT–SBP, HR held constant |\n|---|---|---|---|\n| Naive pairing | −0.24 | −0.45 | −0.10 |\n| Own beat | −0.45 | −0.32 | −0.36 |\n| + sawtooth removed | −0.47 | −0.34 | −0.38 |\n| + global delay | −0.47 | −0.34 | −0.38 |\n| Arterial PAT (reference) | −0.48 | −0.34 | −0.39 |\n\nAlmost all of the gain comes from the pairing; removing the sawtooth adds a little over windows (−0.45 to −0.47)\nand nothing beat to beat, where its 33 ms slips are small next to beat-to-beat noise. The global delay changes\nnothing, as it must. The slope is −0.41 ms of PAT per mmHg of SBP (median over recordings), against −0.34 for\nthe arterial PAT.\n\n[[figure:pat-vs-bp]]\n\n**Between patients, PAT says little about SBP.** Naive PAT's recording medians correlate with heart rate (r\n−0.69) and slightly positively with SBP (+0.08); correctly paired, both are near zero (−0.15 and −0.05). The device\ndelay is the same for every recording, so it neither helps nor hurts between patients.\n",
      "levels": {
        "error": "systematic",
        "population": "Naive pairing turns a cohort's mean hourly PAT–SBP correlation from −0.21 into −0.04, and its PAT–HR correlation into −0.40: a systematic error that changes what a study concludes.",
        "individual": "Each recording's correlation moves by a median −0.16 when pulses are paired with their own beats; for a patient-level PAT–BP calibration, the pairing decides whether there is a signal at all."
      },
      "takeaways": "- Pair each pulse with its own beat before using PAT for anything: in VitalDB the stored delay is longer than the\n  RR interval at ordinary heart rates, and naive pairing makes PAT a heart-rate signal.\n- Test PAT against BP with heart rate held constant; otherwise a correlation may be heart rate's.\n- Over 30 s windows, corrected finger PAT carries as much SBP information as arterial PAT; beat to beat, neither\n  carries much.\n- The result holds in two very different populations (paediatric ICU, adult surgery) with different monitors.\n",
      "limitations": "- Pearson r over an hour mixes slow trends with beat-to-beat noise; the 30 s scale is more informative, but its\n  windows are not independent.\n- SBP comes from the same arterial line whose upstroke gives the reference PAT, so the reference is not\n  independent of the BP measurement.\n- The \"own beat\" window is centred on each recording's typical delay; a recording whose delay changes by more\n  than 0.25 s would be paired wrongly.\n- Ruffolo et al. corrected with their full method (including maximum-likelihood smoothing); here \"corrected\" is\n  own-beat pairing plus sawtooth removal, so the comparison is of methods in spirit, not identical steps.\n",
      "code": [
        "vitaldb-bench/examples/pat_vs_bp.py",
        "vitaldb-bench/examples/pulse_arrival_time.py",
        "vitaldb-bench/examples/run_scan.sh",
        "vitaldb-bench/tools/deck_figures.py"
      ],
      "reproduce": "```\nbash examples/run_scan.sh pat_vs_bp                    # 8 parallel parts, about 15 minutes\npython examples/pat_vs_bp.py agg <work>/examples/pat_vs_bp/part_*.jsonl --out out/pat_bp\n```\n",
      "references": [
        "ruffolo2025high"
      ],
      "figures": [
        {
          "id": "pat-vs-bp-ruffolo",
          "file": "examples/pat-vs-bp-ruffolo.png",
          "theme": "agreement and residuals",
          "source": "vitaldb-bench tools/deck_figures.py",
          "caption": "Pearson r between beat-paired PAT and SBP over an hour per recording, 3,270 recordings, drawn as Ruffolo et al. 2025 appendix B figure 10: distribution (left) and cumulative distribution (right), naive pairing (orange) against corrected (blue).",
          "alt": "Two step histograms, orange centred just below zero and blue shifted to about minus 0.2; two cumulative curves with blue left of orange."
        },
        {
          "id": "pat-vs-bp",
          "file": "examples/pat-vs-bp.png",
          "theme": "agreement and residuals",
          "source": "vitaldb-bench examples/pat_vs_bp.py",
          "caption": "Correlation of PAT with SBP, with heart rate, and with SBP for heart rate held constant, per recording (median and interquartile range), under five timings: beat to beat (A) and over 30 s windows (B).",
          "alt": "Two dot-and-whisker panels; naive pairing (grey) sits near zero for SBP and strongly negative for heart rate, the corrected timings and the arterial reference sit together further down for SBP."
        }
      ],
      "derived": {}
    },
    "ppv-by-breath": {
      "id": "ppv-by-breath",
      "name": "Pulse pressure variation gated by ventilator breaths",
      "summary": "Pulse pressure variation per ventilator breath, in 3,448 recordings: the ventilator's clock sits a median 1.8 s off the arterial line's, which moves half of all breaths' values by a point or more and scatters where in the breath the pulse pressure peaks; a recording's median PPV hardly changes.",
      "side": "time",
      "status": "draft",
      "question": "How large is the respiratory variation in pulse pressure, breath by breath, and how much does it depend on\nputting the ventilator and the arterial line on one timeline?\n",
      "why": "Pulse pressure variation (PPV) is the swing of arterial pulse pressure over one mechanical breath; above about\n13% it predicts a response to fluid ([[reference:michard2000ppv]]). Computed breath by breath, it needs the\nbreath boundaries and the beats on one clock. In VitalDB the boundaries come from the Primus airway pressure\n([[variable:primus-awp]]) and the beats from the SNUADC arterial pressure ([[variable:snuadc-art]]), on two\ndevice clocks that are each corrected against the recorder PC in their own way ([[link:primus-vs-pc]],\n[[link:snuadc-step-vs-pc]]).\n",
      "variables": [
        "primus-awp",
        "snuadc-art",
        "snuadc-ecg-ii"
      ],
      "studies": [
        "ventilator-breath-matching",
        "correction-periods",
        "snuadc-stamp-sawtooth",
        "device-correction-schedules"
      ],
      "observations": [
        "primus-awp-pauses"
      ],
      "data": "Every recording with Primus airway pressure, SNUADC arterial pressure and ECG II and at least 100 mechanical\nbreaths: 3,448 of the 6,329 recordings with both devices, 9.4 million breaths. 1,061 of them also have SNUADC\ncentral venous pressure (CVP), which the alignment needs.\n",
      "steps": "1. Breath onsets from the Primus airway pressure on the Primus's own stamps (rising through 30% of the local\n   range, mechanical breaths only); beats from ECG II, with each beat's pulse pressure from the arterial pressure\n   and its time from the systolic peak.\n2. Put the beats on three timelines: the **recorded** SNUADC stamps (the stamp sawtooth left in); the SNUADC\n   **stamp model** ([[study:snuadc-stamp-sawtooth]]); and the stamp model with the Primus **aligned** to the\n   SNUADC, by the lag between airway pressure and CVP in each 10-minute window\n   ([[study:ventilator-breath-matching]]), interpolated to each breath.\n3. For each breath (2–10 s, at least 3 beats), PPV = (PPmax − PPmin) / mean(PPmax, PPmin). Per recording: the\n   median PPV, how many breaths change between timelines, and where in the breath pulse pressure peaks (a\n   first-harmonic fit over the recording).\n4. Move the aligned breath onsets by −2 to +2 s and count the breaths whose PPV crosses 13%.\n",
      "results": "**The ventilator is seconds off the arterial line.** On the stamp model, the airway pressure leads the CVP by a\nmedian 1.8 s (p10–p90 0.6 to 5.1 s). The lag is not fixed within a recording: it moves by a median 1.5 s over the\nrecording and grows by a median +87 ppm (p10–p90 −49 to +229). The SNUADC here is on its stamp model, on the\nrecorder PC's timeline, so this is the Primus's drift against the PC; between its AWP pauses it is +104 ppm\n([[parameter:primus-drift-breath]]). The Primus's own small corrections do not keep up with it.\n\n**Aligned, pulse pressure peaks where physiology says it should.** With the Primus aligned, the pulse-pressure\nmaximum comes a median 0.84 s after the breath onset, interquartile range 0.40 s, in the 544 recordings where the\nbreath explains enough of the pulse pressure to give a phase. On the recorded stamps the same phase is spread over\nthe whole breath (interquartile range 3.3 s), split into two groups by the wrap-around of the breath cycle.\n\n**Breath by breath, the alignment matters; per recording, it does not.**\n\n| Change of timeline | Breaths whose PPV changes by ≥ 1 point (median per recording) | Breaths crossing 13% | Recordings whose median PPV crosses 13% |\n|---|---|---|---|\n| Recorded stamps → stamp model (SNUADC sawtooth, p95 0.23 s) | 5% | 0.5% | 1 of 3,448 |\n| Stamp model → Primus aligned | 53% | 6.7% | 3 of 1,061 (recorded → aligned) |\n\nMoving the aligned onsets by 0.5 s puts about 2% of breaths on the other side of 13%, and 1 s about 4%; the\nSNUADC's own sawtooth is too small to matter.\n\n**Breaths lost.** The Primus airway pressure stops for about 21 s before each of its clock corrections\n([[observation:primus-awp-pauses]]); there are a median 0.66 gaps longer than 5 s per hour, and the breaths in\nthem have no PPV.\n\n[[figure:ppv-by-breath]]\n",
      "levels": {
        "error": "systematic",
        "population": "Each recording's median PPV hardly moves (3 of 1,061 cross 13%).",
        "individual": "The ventilator sits a median 1.8 s off the arterial line: 53% of breaths' PPV changes by a point or more, and the pulse-pressure peak lands anywhere in the breath."
      },
      "takeaways": "- For PPV breath by breath, or anything else that pairs ventilator events with beats, align the ventilator to\n  the arterial line first; the recorded stamps can be seconds apart, and the offset changes during the\n  recording. The CVP gives the alignment where it exists.\n- A recording-level median PPV is robust to the misalignment: the beats' swing within a breath does not depend\n  on exactly where the breath starts.\n- The phase of the pulse-pressure peak within the breath is a physiological check on the alignment: aligned\n  recordings agree to within a few hundred milliseconds.\n",
      "limitations": "- The alignment uses the airway-pressure-to-CVP lag, which includes any physiological delay between the airway\n  and the central veins (small, but not zero); the aligned timeline is the CVP's, not the true breath onset.\n- Recordings without CVP (69%) have no alignment here; the population lag (median 1.8 s) is a poor substitute,\n  because it varies so much between recordings. Aligning by the arterial pressure itself would be the next step,\n  tested against the CVP.\n- Beats are found on ECG II; pulse pressure is the systolic peak minus the minimum just before the R wave, with\n  no beat-quality labels beyond plausibility limits. Breaths with arrhythmia, spontaneous effort or open chest\n  are not excluded.\n",
      "code": [
        "vitaldb-bench/examples/ppv_by_breath.py",
        "vitaldb-bench/examples/run_ppv_by_breath.sh",
        "vitaldb-bench/tools/vent_sync_pilot.py"
      ],
      "reproduce": "```\nbash examples/run_ppv_by_breath.sh          # 8 parallel parts, about 25 minutes on a laptop\npython examples/ppv_by_breath.py agg <work>/examples/ppv_by_breath/part_*.jsonl --out out/ppv\n```\n",
      "figures": [
        {
          "id": "ppv-by-breath",
          "file": "examples/ppv-by-breath.png",
          "theme": "agreement and residuals",
          "source": "vitaldb-bench examples/ppv_by_breath.py",
          "caption": "Pulse pressure variation by ventilator breath. A: the share of breaths whose PPV crosses 13% when the breath onsets are moved from the Primus-aligned timing (median and interquartile range over 1,061 recordings with CVP). B: where in the breath the pulse pressure peaks, per recording, on the recorded stamps (grey), the SNUADC stamp model (blue) and with the Primus aligned by CVP (orange). C: each recording's median PPV is nearly the same on the recorded stamps as aligned.",
          "alt": "Left, a V-shaped curve rising from zero at no shift to about 8% at two seconds either way; middle, a sharp orange peak just under one second and broad grey and blue groups near minus two and plus two seconds; right, points on the identity line."
        }
      ],
      "derived": {}
    },
    "propofol-against-pump": {
      "id": "propofol-against-pump",
      "name": "Charted propofol dose against the infusion pump",
      "summary": "The charted propofol bolus total against the volume the Orchestra pump records, as a check of the propofol heaping model and as a dose in mg/kg with its uncertainty.",
      "side": "value",
      "status": "planned",
      "question": "How close is the charted propofol dose to what the pump delivered?\n",
      "why": "The charted dose is heaped ([[study:value-model-intraop-ppf]]). Where a pump also delivered propofol its volume is\nrecorded independently, which gives a rare check of a rounding model against an independent record.\n",
      "variables": [
        "intraop-ppf",
        "pump-rates",
        "weight"
      ],
      "studies": [
        "value-model-intraop-ppf"
      ],
      "takeaways": "Planned, but weaker than hoped. The Orchestra propofol pump is in about half the recordings (458 of 846 sampled),\nbut only 2 of those 846 also have a charted propofol bolus: pump cases are infusions, and the charted total is\nthe manual bolus. The two records mostly describe different doses, so this needs the pump's own bolus deliveries,\nnot its total volume.\n",
      "derived": {}
    },
    "pulse-arrival-time": {
      "id": "pulse-arrival-time",
      "name": "Pulse arrival time in two correction steps",
      "summary": "The time from the ECG R wave to the pulse at the finger, as stored, after the PLETH's sawtooth is removed, and after its device offset is removed, smoothed by maximum likelihood, with the arterial line as the reference.",
      "side": "time",
      "status": "draft",
      "question": "How long does the pulse take to travel from the heart to the finger, and how does that change through an\noperation?\n",
      "why": "Pulse arrival time (PAT) is a difference of two event times taken from two signals, so any delay or drift between\nthem goes straight into it. ECG and arterial pressure come through the same SNUADC converter and stay aligned\n([[observation:snuadc-one-clock]], [[study:ppg-art-reference]]). The PLETH does not, in two ways:\n\n- **a sawtooth**: its delay behind the ECG creeps at 18–52 ppm and jumps back by one 33.3 ms frame at each slip,\n  every 11–31 minutes ([[observation:ppg-sawtooth]], [[study:ppg-sawtooth]]);\n- **an offset**: the monitor delivers the PLETH about 0.4 s late ([[observation:ppg-hardware-delay]],\n  [[study:ppg-offset-reference]]).\n\nBoth look like physiology in a raw PAT series: the sawtooth as slow drifts and sudden jumps of tens of\nmilliseconds, the offset as a PAT almost three times too long, which also differs between recording setups.\n",
      "variables": [
        "snuadc-ecg-ii",
        "snuadc-pleth",
        "snuadc-art"
      ],
      "studies": [
        "ppg-sawtooth",
        "ppg-sawtooth-pauses",
        "ppg-art-reference",
        "ppg-offset-reference",
        "ppg-frame-clock"
      ],
      "observations": [
        "ppg-sawtooth",
        "ppg-hardware-delay",
        "snuadc-one-clock",
        "pleth-30hz-frames",
        "fiducial-quantisation",
        "ppg-sawtooth-pauses"
      ],
      "data": "60 minutes per case, starting 10 minutes into the overlap of ECG lead II and PLETH. The cohort is a random 120\nof the recordings with both signals (97% of recordings have them: 817 of 846 sampled), plus [[case:5975]] for\nthe figure; 66 of the 121 also have a pulsatile arterial line, which serves as the reference.\n",
      "steps": "1. **Fiducials.** R peaks in ECG lead II; the steepest upstroke after each R peak in the PLETH, and in the\n   arterial pressure where there is a line.\n2. **Step 1, remove the sawtooth** ([[observation:ppg-sawtooth]]). `physiosync.fit_ecg_ppg_delay` fits the PLETH's\n   delay as an offset plus a sawtooth (creep at a fixed rate, reset by one 30 Hz frame at each slip), with the arterial upstrokes as the\n   reference where present. Each tooth gets a confidence tier; teeth below tier B are left as they are.\n   Subtracting only the sawtooth leaves the PAT at its stored level.\n3. **Step 2, remove one global device delay** ([[observation:ppg-hardware-delay]]): 385 ms for every recording.\n   The stored ECG-to-PLETH delay has one peak over the whole release, with no clusters by recording setup and no\n   shift along the case order ([[study:pat-offset-census]]). The adopted mean finger PAT, 0.27 s\n   ([[study:ppg-offset-reference]]), is measured to the foot of the pulse, so the delay is too: 655 ms (the stored\n   delay to the foot) minus 270 ms. This example times the steepest upstroke, 59 ms after the foot, so its corrected\n   PAT reads about 270 + 59 = 330 ms. Because the same delay comes off every case, differences in PAT between\n   patients are kept; only the absolute level rests on the adopted 0.27 s.\n4. **Smooth by maximum likelihood**, after [[reference:ruffolo2025high]]: per 30 s window, the latent PAT that\n   maximises the likelihood of the window's beats under the recording's per-beat error model\n   (`physiosync.pat_measurement_model`: the PLETH fiducial's frame rounding or jitter, and the ECG's one-sample\n   rounding), evaluated by Monte Carlo, with a 2% floor for mis-detected beats. A median gives every beat the\n   same weight whatever its error; the likelihood weighs them by the error model and ignores outliers.\n",
      "results": "**One case.** In [[case:5975]] the stored PAT (grey) is about 0.68 s and jumps back by one frame three times in\nthe hour (dotted). After step 1 (blue) the jumps and ramps are gone and the series has the same course as the\narterial PAT (green): the spread of finger minus arterial PAT, minute by minute, falls from 10.7 to 2.7 ms.\nStep 2 (orange) moves the whole series down by the global 385 ms, without changing its shape; the corrected finger\nPAT (to the upstroke) then sits 116 ms above the arterial PAT in this case.\n\n[[figure:pat-case]]\n\n**The cohort (121 cases).**\n\n- *Global delay (step 2).* The stored PAT's case median is 0.65–0.75 s; step 2 subtracts 385 ms from every case,\n  so the corrected case medians keep their spread (median 333 ms to the upstroke, interquartile range 319–347 ms). Eight cases\n  have a stored median of 0.44–0.54 s: their pulses were paired with the wrong beat (fast heart rates or doubled\n  R peaks, with a 0.7 s delay longer than one RR interval; see [[study:pat-offset-census]]). They are left out of\n  the cohort figures, leaving 113.\n- *Sawtooth (step 1).* A median of 4 slips an hour. At least one tooth was corrected with confidence (tier A or B)\n  in 46 of the 121 cases. Where there was also an arterial line (24 cases), the spread of finger minus arterial\n  PAT fell from a median of 13.0 to 9.8 ms per case, and improved in 19 of the 24; pooled over their windows,\n  from 13.7 to 9.8 ms. The within-case spread of PAT itself changes little (17.7 to 16.9 ms in the median case),\n  because real changes in PAT, tens of milliseconds as blood pressure changes, are larger than the sawtooth.\n\n[[figure:pat-cohort]]\n\n**Before and after, as PAT values.** As stored, ECG-to-finger PAT is 718 ms in the median window (638–768 ms,\n5th to 95th percentile), far beyond any physiological value: almost every window lies outside the 200–285 ms\nreported for awake adults, even allowing for the 59 ms from foot to upstroke. After both steps the median is 333\nms to the upstroke (253–382 ms), above the arterial-line PAT (223 ms), as a finger, further from the heart than\nthe radial artery, should be. Where the orange peak sits overall rests on the adopted 0.27 s; its spread is\nmeasured, because one delay comes off every case. 1.9% of corrected windows come out negative, where beats were\nmis-paired; they are left out of the figure. The adult range is provisional and still has to be\nverified (see the open questions).\n\n[[figure:pat-distribution]]\n\n**Against the arterial line, window by window.** In the 60 cases with an arterial line, finger PAT as stored sits\n496 ms above the arterial-line PAT in the median window; after both steps it sits 111 ms above it (upstroke to\nupstroke; about 90 ms foot to foot in the census), the order expected for a finger further from the heart than the\nradial artery. Both clouds slope with the arterial PAT:\nacross cases, the corrected finger PAT follows the arterial PAT with a slope of 0.59 (correlation 0.53), exactly as\nthe stored one does, because a single global delay keeps the differences between patients. (An earlier version\nof step 2 removed each case's own offset, which flattened the corrected cloud and threw those differences away.)\n\n[[figure:pat-scatter]]\n",
      "levels": {
        "error": "systematic",
        "population": "A device delay of about 0.39 s sits in every stored PAT: a cohort mean is wrong by that much until it is removed.",
        "individual": "The PLETH sawtooth adds slips of 33 ms within each patient; removing it lowers the spread against the arterial line from 13.0 to 9.8 ms."
      },
      "takeaways": "- Never read the absolute ECG-to-PLETH PAT in VitalDB as physiology: it carries a device delay of about 0.39 s.\n  The delay is the same across recording setups and the case order, so one global value removes it and keeps\n  differences between patients; the absolute level still rests on an adopted mean finger PAT.\n- Pair each pulse with its own beat: the stored delay (about 0.7 s) is longer than one RR interval at fast heart\n  rates, and a nearest-upstroke search then pairs one beat out.\n- Within a case, remove the sawtooth (step 1) before reading changes over minutes; otherwise slips appear as\n  sudden PAT changes of 33 ms.\n- Check each tooth's tier: below B only the offset is removed, and the sawtooth is still there.\n- Where an arterial line exists, ECG to arterial line is a PAT without either problem.\n",
      "limitations": "- The beat detector is deliberately simple: 12% of 30 s windows lie more than 40 ms from their case's median,\n  mostly from mis-detected beats.\n- 60 minutes of 121 cases; a full run belongs in the runbook.\n",
      "open_questions": [
        {
          "title": "Most teeth cannot be corrected with confidence",
          "kind": "next step",
          "summary": "In 75 of 121 cases no tooth reached tier B, so the sawtooth stays in them. The likeliest causes, in order: the example gives the fit no creep-rate prior, although the rate is fixed per setup ([[observation:ppg-sawtooth]], the Recording setups page), so a tooth reaches tier A only if the recording has two full teeth that agree; 60-minute windows hold few full teeth (on up to 3 hours, cases with a confident tooth rose from 47% to 57% and confident teeth from 21% to 33%); tier C teeth (the most common) are fitted but not applied; and a simple PLETH fiducial produces false slips (some cases show 24–46 an hour against about 4). Setup priors and longer windows come first; the tier rules were tuned on injected sawtooths, not on recordings."
        },
        {
          "title": "Where the sawtooth stops",
          "kind": "phenomenon",
          "summary": "In some stretches no slip comes although several were due, and the delay is flat ([[study:ppg-sawtooth-pauses]]). The model treats the stretch before and after a pause as creep then hold, of unknown length, which leaves up to one frame of uncertainty there."
        },
        {
          "title": "Pairing by the modal delay in the example itself",
          "kind": "next step",
          "summary": "Eight of 121 cases were paired one beat out (fast heart rates or doubled R peaks) and are left out of the cohort figures. The census pairs pulses by each segment's modal delay instead ([[study:pat-offset-census]]); using that pairing here would bring them back."
        },
        {
          "title": "The adopted 0.27 s",
          "kind": "assumption",
          "summary": "The global delay is the release's median stored delay to the pulse foot (655 ms) minus 0.27 s, a mean R-to-foot finger PAT for anaesthetised adults from studies that sampled ECG and PPG on one clock ([[study:ppg-offset-reference]]). Differences between patients and changes within a case are measured; the absolute level of every case moves with the adopted value. It implies a finger-minus-radial difference of about 90 ms foot to foot, which a literature value could check."
        },
        {
          "title": "What plausible PAT range should be shown for anaesthetised adults?",
          "kind": "assumption",
          "summary": "The figure shades 200–285 ms to the foot (shifted by 59 ms to the upstroke), the range of means in shared-clock studies of awake adults ([[study:ppg-offset-reference]]). That is a range of study averages, not of individual windows, and anaesthesia lengthens PAT by about 40 ms. A verified range for adults under anaesthesia, ideally of individual values, is still needed; [[reference:ruffolo2025high]] gives one for critically ill children."
        },
        {
          "title": "The maximum-likelihood smoother follows Ruffolo et al. in approach, not in detail",
          "kind": "next step",
          "summary": "The window length (30 s), the outlier floor (2%) and the error model's terms are this example's choices; the estimator has not been checked against the original implementation, or against injected PAT changes of known size."
        }
      ],
      "code": [
        "vitaldb-bench/examples/pulse_arrival_time.py",
        "physiosync/physiosync/ecg_ppg.py",
        "physiosync/physiosync/measurement.py"
      ],
      "reproduce": "```\npython examples/pulse_arrival_time.py --root <VitalDB release> --cases 5975 --figure 5975 --out out/pat\npython examples/pulse_arrival_time.py --root <VitalDB release> --cases <case ids> --out out/pat\npython examples/pulse_arrival_time.py --out out/pat --cohort-figure\n```\nwith [[software:physiosync]] and the TimeNet VitalDB reader installed (see [[software:vitaldb-bench]]).\n",
      "references": [
        "ruffolo2025high"
      ],
      "figures": [
        {
          "id": "pat-case",
          "file": "examples/pat-case-5975.png",
          "theme": "example traces",
          "source": "vitaldb-bench examples/pulse_arrival_time.py",
          "records": [
            5975
          ],
          "caption": "Pulse arrival time in [[case:5975]], 30 s maximum-likelihood estimates over an hour, on one axis from zero: ECG to finger as stored (grey), after step 1 removes the sawtooth (blue), after step 2 removes the global 385 ms device delay (orange), and ECG to the arterial line, which needs no correction (green). Dotted lines mark frame slips.",
          "alt": "Four lines on one axis; grey with a sawtooth and blue without it near 690 ms, orange near 290 ms and green near 175 ms, the last three with the same slow shape."
        },
        {
          "id": "pat-cohort",
          "file": "examples/pat-cohort.png",
          "theme": "delay and window fits",
          "source": "vitaldb-bench examples/pulse_arrival_time.py",
          "caption": "113 cases with ECG and PLETH. Left: PAT in 30 s windows around each case's median, as stored and after step 1. Middle: each case's median PAT as stored and after step 2 subtracts the global 385 ms; the spread is kept. Right: finger minus arterial PAT in the cases with an arterial line and a confidently corrected tooth, as stored and after step 1.",
          "alt": "Three histograms; the left one barely narrows after step 1, the middle one shows the same spread of case medians around 700 ms as stored and around 330 ms after step 2, the right one narrows clearly after step 1."
        },
        {
          "id": "pat-distribution",
          "file": "examples/pat-distribution.png",
          "theme": "distributions of measured parameters",
          "source": "vitaldb-bench examples/pulse_arrival_time.py",
          "caption": "PAT in 30 s windows over 113 cases: ECG to finger as stored (grey) and after steps 1 and 2 (orange), and ECG to the radial arterial line in the 60 cases with one (green). The shaded band is the 200–285 ms (R to finger foot) reported for awake adults in shared-clock studies, shifted by the 59 ms foot-to-upstroke time to match this example's fiducial; it is provisional and still to be verified. Negative corrected values (1.9% of windows) are left out.",
          "alt": "Three histograms on one axis; grey centred near 716 ms, orange near 333 ms and green near 223 ms, with a shaded band from 259 to 344 ms."
        },
        {
          "id": "pat-scatter",
          "file": "examples/pat-scatter.png",
          "theme": "agreement and residuals",
          "source": "vitaldb-bench examples/pulse_arrival_time.py",
          "caption": "ECG-to-finger PAT against ECG-to-radial-artery PAT in the same 30 s windows (7,081 windows, 60 cases), as stored (grey) and after steps 1 and 2 (orange); both axes start at zero, and the dashed line is equal PAT. Negative corrected values are left out.",
          "alt": "A scatter with a grey cloud near 700 ms on the vertical axis and an orange cloud near 270 ms, just above the dashed equality line, both spread between 170 and 300 ms on the horizontal axis."
        }
      ],
      "derived": {}
    },
    "regression-with-rounding": {
      "id": "regression-with-rounding",
      "name": "A regression that carries rounding uncertainty through",
      "summary": "Acute kidney injury after surgery against intraoperative urine output (mL/kg/h), fitted once on the recorded numbers and once per draw from the true-value distributions: the rounding moves 14% of cases across the oliguria threshold, but widens the confidence interval of the cohort's regression by at most 4%.",
      "side": "value",
      "status": "draft",
      "question": "Is lower intraoperative urine output associated with acute kidney injury after surgery, and how much of the\nuncertainty in the answer comes from the rounding of the inputs?\n",
      "why": "The exposure is a ratio of three rounded numbers ([[example:urine-output-per-kg-per-hour]]): urine output heaped\non 50 and 100 mL, weight, and anaesthesia times on 5- and 10-minute marks. Rounded inputs act like measurement\nerror ([[reference:carroll2006measurement]]): they can flatten a slope (regression dilution), and an interval\ncomputed from the recorded numbers leaves their uncertainty out. Fitting the model once per draw from the\ntrue-value distributions and combining the fits by Rubin's rules, as in multiple imputation\n([[reference:rubin1987multiple]]), carries it through.\n",
      "variables": [
        "intraop-uo",
        "weight",
        "ane-duration",
        "preop-cr",
        "cr"
      ],
      "studies": [
        "value-model-intraop-uo",
        "value-model-weight",
        "value-model-anestart",
        "value-model-aneend",
        "timestamp-derived-intervals",
        "distribution-api-validation"
      ],
      "data": "The 3,042 cases with a positive urine output, a weight, both anaesthesia times, a preoperative creatinine and at\nleast one creatinine in the 48 hours after the end of surgery. 225 (7.4%) have acute kidney injury by the KDIGO\ncreatinine criterion ([[reference:kdigo2012aki]]): the highest postoperative creatinine at least 0.3 mg/dL above\nthe preoperative value, or at least 1.5 times it. Postoperative creatinine is reported to 0.01 mg/dL and is used\nas recorded.\n",
      "steps": "1. For each case, draw 200 true values of urine output, weight, anaesthesia duration and preoperative creatinine\n   from the pinned vitaldb-resolution library (v0.6.0), and form the urine output rate for each draw.\n2. Fit three unadjusted models on the recorded numbers (naive):\n   **A.** logistic, kidney injury against the rate (per 1 mL/kg/h, capped at 5);\n   **B.** logistic, kidney injury against oliguria (rate below 0.5 mL/kg/h);\n   **C.** linear, log of the highest postoperative over the preoperative creatinine against the rate.\n3. Fit the same three models once per draw (kidney injury recomputed with that draw's preoperative creatinine),\n   and combine the 200 fits by Rubin's rules: the estimate is their mean, and the variance adds the spread\n   between draws to the mean within-fit variance.\n4. Compare: the estimate, the 95% interval, and the share of the total variance that comes from the rounding\n   (the between-draw part).\n",
      "results": "**No association, either way.** The models are unadjusted, so the clinical answer is not the point; it is the\nsame with and without the rounding:\n\n| Model | Recorded numbers | Carried through (200 draws) | Share of variance from rounding |\n|---|---|---|---|\n| A. Odds ratio per 1 mL/kg/h | 1.05 (0.91 to 1.21) | 1.04 (0.90 to 1.20) | 0.7% |\n| B. Odds ratio if oliguric | 0.94 (0.71 to 1.25) | 0.97 (0.72 to 1.31) | 6.3% |\n| C. Log creatinine ratio per 1 mL/kg/h | −0.003 (−0.013 to 0.008) | −0.003 (−0.013 to 0.007) | 0.2% |\n\n**Where the rounding shows.** In 413 of the 3,042 cases (14%) the draws put the rate on both sides of\n0.5 mL/kg/h, so oliguria is not a fixed property of those cases. That is what widens model B's interval\n(by 4%) and moves its estimate. With the rate as a number (A and C), each case's uncertainty is small\nnext to the spread of rates between cases, and the rounding adds almost nothing. The fit on the recorded numbers\nin model A lies at the edge of the fits on the draws: rounding shifts it slightly, though far less than its\nconfidence interval. Kidney injury itself changes with the preoperative creatinine's rounding in only 7 cases.\n\n[[figure:regression-rounding]]\n\n**Does the threshold matter?** Moving the oliguria threshold from 0.2 to 1.5 mL/kg/h changes how many cases\ncross it between draws (6% to 14%), but never gives the rounding more than a small share of a cohort's\nuncertainty:\n\n| Threshold (mL/kg/h) | Oliguric (recorded) | Cases crossing between draws | Rounding's share of the variance: prevalence | Rounding's share of the variance: odds ratio |\n|---|---|---|---|---|\n| 0.2 | 7.2% | 8% | 11% | 5% |\n| 0.3 | 15.9% | 12% | 11% | 8% |\n| 0.5 | 35.5% | 14% | 8% | 6% |\n| 0.75 | 55.1% | 12% | 5% | 5% |\n| 1.0 | 67.7% | 10% | 5% | 5% |\n| 1.5 | 83.1% | 6% | 4% | 4% |\n\nNor do the subgroups where the rounding is worst: among cases with a round 50 or 100 mL (200 mL or less), 42%\ncross 0.5 between draws, yet the rounding is still only 18% of the variance of their prevalence. Each case's\nerror is independent and nearly symmetric, so over hundreds of cases it cancels; for one case it does not\n([[example:oliguria-labels]]).\n",
      "levels": {
        "error": "random",
        "population": "Carried through 200 draws, the rounding is under 8% of the variance at any threshold: the cohort's answer does not change.",
        "individual": "14% of patients sit on both sides of 0.5 mL/kg/h between draws, which is what the cohort averages over."
      },
      "takeaways": "- Carry rounding through a regression by fitting once per draw and combining with Rubin's rules; the fraction of\n  the variance that comes from the draws says how much the rounding matters for that question.\n- A threshold is where rounding matters most: dichotomising the exposure (oliguria) gave rounding ten times the\n  weight it has with the rate as a number.\n- For a cohort-level question VitalDB's rounding is small next to the spread between patients, whatever the\n  threshold, because independent errors cancel; only a systematic error would not. For a label or decision per\n  patient it matters ([[example:oliguria-labels]], [[example:urine-output-per-kg-per-hour]]).\n",
      "limitations": "- The draws come from the distribution of the true value given the recorded one, without using the outcome. For\n  measurement-error correction the imputation should also condition on the outcome\n  ([[reference:cole2006mime]]); without it the combined slope is pulled slightly towards zero. Here the rounding\n  is small enough that this hardly matters.\n- Unadjusted models, and creatinine-based kidney injury only (no urine criterion, no baseline other than the\n  preoperative value). The clinical question needs adjustment for case mix.\n- The inputs are drawn independently; the duration's two ends share the anaesthesia record's clock.\n",
      "code": [
        "vitaldb-bench/examples/regression_with_rounding.py"
      ],
      "reproduce": "```\npython examples/regression_with_rounding.py --resolution <vitaldb-resolution at the pinned commit> \\\n    --root <VitalDB release> --out out/regression --draws 200\n```\nUnder a minute on a laptop.\n",
      "figures": [
        {
          "id": "regression-rounding",
          "file": "examples/regression-rounding.png",
          "theme": "agreement and residuals",
          "source": "vitaldb-bench examples/regression_with_rounding.py",
          "caption": "Three regressions of kidney injury or creatinine change on urine output, each fitted 200 times on draws from the true-value distributions (histogram of the 200 estimates), combined by Rubin's rules (blue, 95% interval) and fitted once on the recorded numbers (grey). The draws' spread is narrow next to each interval, except for the oliguria model (B), where 14% of cases cross the threshold between draws.",
          "alt": "Three panels each with a narrow histogram and two horizontal confidence-interval bars of nearly equal width; the middle panel's histogram is wider."
        }
      ],
      "derived": {}
    },
    "urine-output-per-kg-per-hour": {
      "id": "urine-output-per-kg-per-hour",
      "name": "Urine output in mL/kg/h as a distribution",
      "summary": "Intraoperative urine output per kilogram per hour, from three rounded inputs, as a probability distribution per case: one case in ten cannot be placed confidently on either side of 0.5 mL/kg/h.",
      "side": "value",
      "status": "draft",
      "question": "Was this patient's urine output below 0.5 mL/kg/h during the operation?\n",
      "why": "The answer is a ratio of three recorded numbers, each rounded in its own way: urine output heaped on 50 and\n100 mL ([[study:value-model-intraop-uo]]), weight on whole kilograms or a scale's 0.1 or 0.05 kg\n([[study:value-model-weight]]), and the anaesthesia start and end on 5- and 10-minute marks\n([[study:timestamp-derived-intervals]]). Computed from the recorded numbers, the rate is one number; the true rate\ncan lie on either side of a threshold.\n",
      "variables": [
        "intraop-uo",
        "weight",
        "ane-duration"
      ],
      "studies": [
        "value-model-intraop-uo",
        "value-model-weight",
        "value-model-anestart",
        "value-model-aneend",
        "timestamp-derived-intervals"
      ],
      "data": "The 3,645 cases with a positive urine output, a weight and both anaesthesia times (39 more with a positive urine\noutput lack one of the others or have no distribution for it). Recorded zeros are left out until the zero rule is\nin the library.\n",
      "steps": "1. For each case, draw 4,000 true values of urine output, weight and anaesthesia duration from the pinned\n   vitaldb-resolution library (v0.6.0, model version 2: urine output conditioned on the case's anaesthesia\n   duration and department; `Registry.case_distribution`).\n2. Form urine output / weight / duration in hours for each draw.\n3. Summarise each case by its median, a 95% interval and P(rate < 0.5 mL/kg/h); class it as confidently below\n   (P > 0.95), confidently above (P < 0.05) or uncertain.\n4. Compare with the single rate computed from the recorded numbers.\n",
      "results": "**Four cases.** The distributions differ in width as much as in position. [[case:5111]] (100 mL, 66 kg, 190 min)\nsits on the threshold with a wide distribution, because a recorded 100 mL may be anything from about 50 to 150 mL:\nP(below 0.5) = 0.62, 95% interval 0.32 to 0.63 mL/kg/h. [[case:111]] is close to the threshold but confidently below; [[case:113]], with 320 mL\n(written to the nearest 10), is narrow and confidently above.\n\n[[figure:uo-examples]]\n\n**The cohort.** Of 3,645 cases, 1,086 are confidently below 0.5 mL/kg/h, 2,178 confidently above, and 381 (10%)\nuncertain. Nearly all the uncertain ones (365) have a recorded rate between 0.35 and 0.7, where they are a third\nof the cases. Of the 1,276 cases the recorded numbers put below 0.5, 190 are not confidently below; of those they\nput above, 191 are not confidently above. The 95% interval is 14% of the rate in the median case (4% to 73%, 10th to 90th\npercentile); the widest are cases with a round urine output of 50 or 100 mL.\n\n[[figure:uo-cohort]]\n",
      "levels": {
        "error": "random",
        "population": "The rounding adds spread but almost no bias: the median of the distributions sits within a hair of the recorded rate.",
        "individual": "381 of 3,645 patients (10%) cannot be placed confidently on either side of 0.5 mL/kg/h; with a round 50 or 100 mL, many more."
      },
      "takeaways": "- A threshold on a derived rate misclassifies cases near it in a way the recorded numbers cannot show. Report the\n  share that is uncertain, or carry P(below) into the analysis instead of a yes or no.\n- Most of the uncertainty comes from urine output recorded as a round 50 or 100 mL; weight and duration add\n  little.\n",
      "limitations": "- The three inputs are drawn independently; the duration's two ends share the anaesthesia record's clock.\n- Recorded zeros (23 cases) are left out; the library's zero rule (v0.6.0) gives them a point mass and a flag, not\n  a rate.\n",
      "code": [
        "vitaldb-bench/examples/urine_output_rate.py"
      ],
      "reproduce": "```\npython examples/urine_output_rate.py --resolution <vitaldb-resolution at the pinned commit> \\\n    --root <VitalDB release> --out out/uo --figure 5111 111 113 305\n```\n",
      "figures": [
        {
          "id": "uo-examples",
          "file": "examples/uo-examples.png",
          "theme": "distributions of measured parameters",
          "source": "vitaldb-bench examples/urine_output_rate.py",
          "records": [
            111,
            5111,
            113,
            305
          ],
          "caption": "Urine output rate as a distribution in four cases: the distribution (blue), the rate from the recorded numbers (dashed) and 0.5 mL/kg/h (orange). [[case:5111]] straddles the threshold; [[case:111]] is close to it but confidently below; [[case:113]] and [[case:305]] are above.",
          "alt": "Four histograms of urine output rate; the second one is centred on the 0.5 line, the first lies just below it, the last two above it."
        },
        {
          "id": "uo-cohort",
          "file": "examples/uo-cohort.png",
          "theme": "distributions of measured parameters",
          "source": "vitaldb-bench examples/urine_output_rate.py",
          "caption": "3,645 cases. Left: P(true rate below 0.5 mL/kg/h) against the rate from the recorded numbers; grey cases are uncertain. Right: width of the 95% interval relative to the median.",
          "alt": "A scatter that falls from 1 to 0 across 0.5 with a band of grey points around it, and a histogram of relative widths with peaks near 0.1, 0.25 and 0.75."
        }
      ],
      "derived": {}
    }
  },
  "software": {
    "analysis-workspace": {
      "id": "analysis-workspace",
      "name": "Analysis workspace",
      "summary": "One-off scripts kept on the analysis machine beside their per-case output. Not published; the useful ones move into vitaldb-bench.",
      "visibility": "not yet released",
      "local": "../../../../vitalDB/work",
      "description": "Some checks were written as short scripts next to the per-case results they produce, because those results\nmay not leave the analysis machine. Their paths are listed so the work can be found again; they have no links.\nA script that a study depends on should move into [[software:vitaldb-bench]] `tools/`, where it is reviewed and\npublished.\n",
      "related": [
        "vitaldb-bench"
      ],
      "derived": {}
    },
    "lineaccess": {
      "id": "lineaccess",
      "name": "lineaccess",
      "summary": "Finds accesses to invasive pressure lines (flushes, blood draws, zeroing) in the pressure waveform itself.",
      "repo": "https://github.com/metrology-atlas/lineaccess",
      "branch": "main",
      "visibility": "public",
      "local": "../../../lineaccess",
      "licence": "MIT",
      "language": "Python 3.10+ (numpy)",
      "description": "When an arterial line is accessed the transducer stops seeing the patient: a flush reads about 300 mmHg with no\npulse, a stopcock open to air reads about 0 mmHg, and a line closed to the patient shows a pressure with no\npulse. lineaccess finds and classifies these stretches. It is independent of [[software:physiosync]].\n",
      "role": "Line accesses mark the moments blood is drawn, so they give lab samples a draw time on the waveform clock\n([[study:arterial-line-accesses]], [[study:lab-timestamp-meaning]]).\n",
      "related": [
        "vitaldb-bench",
        "physiosync"
      ],
      "references": [
        "harris2020numpy",
        "pytest"
      ],
      "derived": {
        "studies": [
          "arterial-line-accesses"
        ],
        "observations": [
          "cuff-occludes-line"
        ],
        "mentioned_in": [
          "study:arterial-line-accesses",
          "observation:cuff-occludes-line",
          "software:vitaldb-bench"
        ]
      }
    },
    "metrology-atlas": {
      "id": "metrology-atlas",
      "name": "metrology-atlas",
      "summary": "The engine that turns this dataset's YAML files into the atlas JSON and this site.",
      "repo": "https://github.com/metrology-atlas/metrology-atlas",
      "branch": "main",
      "visibility": "public",
      "local": "../..",
      "licence": "MIT",
      "language": "Python 3.11+ (pyyaml); the site is built with Zensical",
      "description": "Nothing in the engine is specific to VitalDB. A dataset is a folder of YAML files, one per item, which the\nengine checks (types, references, figures, code paths, forbidden patterns such as record identifiers),\ncompiles to one JSON file, and writes as this site, including the planner and the accuracy chart.\n",
      "install": "```bash\npython -m metrology_atlas check datasets/vitaldb\npython -m metrology_atlas json  datasets/vitaldb --out build/vitaldb.metrology.json\npython -m metrology_atlas site  datasets/vitaldb --out build/vitaldb     # then: zensical build, in that folder\n```\n",
      "parts": [
        {
          "path": "metrology_atlas/model.py",
          "summary": "The schema: every kind of item and its fields."
        },
        {
          "path": "metrology_atlas/load.py",
          "summary": "Loads and checks a dataset folder, and derives the relations stated on the other side."
        },
        {
          "path": "metrology_atlas/code.py",
          "summary": "Code references and their links into repositories."
        },
        {
          "path": "metrology_atlas/site.py",
          "summary": "Writes the site, one page per item."
        },
        {
          "path": "metrology_atlas/planner.py",
          "summary": "The synchronisation planner."
        },
        {
          "path": "metrology_atlas/chart.py",
          "summary": "The accuracy-against-resolution chart."
        }
      ],
      "references": [
        "pyyaml",
        "zensical",
        "pytest"
      ],
      "derived": {}
    },
    "physiosync": {
      "id": "physiosync",
      "name": "physiosync",
      "summary": "Timing corrections for multi-signal physiological recordings: the stamp sawtooth, hidden frame clocks, the ECG-to-PPG delay and its sawtooth, and the resolution of fiducial points. Dataset-neutral.",
      "repo": "https://github.com/metrology-atlas/physiosync",
      "branch": "main",
      "visibility": "public",
      "local": "../../../physiosync",
      "licence": "MIT",
      "language": "Python 3.10+ (numpy, scipy)",
      "description": "physiosync works on times and samples it is given. It reads no file format and assumes no particular dataset,\nmonitor, sampling rate or frame rate; a dataset's import code calls it and stores the results (corrected\ntiming and quality labels). Corrections are applied only where they are corroborated, and each comes with a\nconfidence tier.\n",
      "role": "It holds the general methods that several VitalDB studies rely on: the stamp-clock fit\n([[study:snuadc-stamp-sawtooth]]), the frame-clock search ([[study:ppg-frame-clock]]), the ECG-PPG sawtooth\nremoval ([[study:ppg-sawtooth]]) and fiducial resolution ([[observation:fiducial-quantisation]]).\n[[software:vitaldb-bench]] scripts call it on VitalDB cases.\n",
      "parts": [
        {
          "path": "physiosync/stamps.py",
          "summary": "Record stamps that follow a sample clock between corrections; fits the rate and offsets and removes the stamp sawtooth."
        },
        {
          "path": "physiosync/frames.py",
          "summary": "Finds a hidden frame clock at any rate, from the spectrum of sample-to-sample change and fiducial locking."
        },
        {
          "path": "physiosync/quantisation.py",
          "summary": "Effective temporal resolution of fiducial points, on whole-sample grids or partly locked to a period."
        },
        {
          "path": "physiosync/ecg_ppg.py",
          "summary": "The ECG-to-PPG delay; fits and removes the frame-slip sawtooth, with confidence tiers."
        },
        {
          "path": "physiosync/labels.py",
          "summary": "One label per sawtooth tooth, with its correction, resolution and confidence."
        },
        {
          "path": "physiosync/chains.py",
          "summary": "Rate-consistent chains of slips, as lower-confidence labels."
        },
        {
          "path": "physiosync/measurement.py",
          "summary": "The PAT measurement model of a recording, for downstream estimators."
        },
        {
          "path": "physiosync/pairing.py",
          "summary": "Beat pairing between ECG and PPG."
        },
        {
          "path": "physiosync/reference.py",
          "summary": "Published pulse arrival times by study and hardware."
        }
      ],
      "related": [
        "vitaldb-bench",
        "lineaccess"
      ],
      "references": [
        "harris2020numpy",
        "virtanen2020scipy",
        "hrvmatch",
        "pytest"
      ],
      "derived": {
        "studies": [
          "ppg-frame-clock",
          "ppg-sawtooth",
          "snuadc-stamp-sawtooth"
        ],
        "observations": [
          "fiducial-quantisation",
          "pleth-30hz-frames",
          "ppg-sawtooth"
        ],
        "examples": [
          "pulse-arrival-time"
        ],
        "mentioned_in": [
          "variable:snuadc-pleth",
          "link:pleth-content-vs-frames",
          "study:loop-closure",
          "study:monitor-lines",
          "study:ppg-art-reference",
          "study:snuadc-stamp-sawtooth",
          "observation:pleth-30hz-frames",
          "observation:ppg-sawtooth",
          "example:pulse-arrival-time",
          "software:lineaccess",
          "software:vitaldb-bench"
        ]
      }
    },
    "timenet": {
      "id": "timenet",
      "name": "TimeNet VitalDB reader",
      "summary": "The reader that loads VitalDB's .vital files with this atlas's corrections applied: records after each reset put back in order, every sample placed by its count, the corrected scale factors and the whole-file time zero.",
      "repo": "https://github.com/OpenTSLM/TimeNet",
      "branch": "main",
      "visibility": "public",
      "local": "../../../TimeNet",
      "docs": "https://docs.timenet.ai",
      "language": "Python",
      "description": "The VitalDB reader is one of the dataset connectors in the open TimeNet project (`timenet-connectors`). It reads\n.vital files into records and tracks with exact sample times. The atlas uses it as the place where its findings\nabout the files are applied on reading, so an analysis that loads VitalDB through it starts from corrected data.\n",
      "role": "What the reader does about the atlas's observations:\n\n- **Reset blocks out of order.** It puts the five records after each SNUADC reset back in sampling order\n  (on by default; `VITALDB_RESET_ORDER=off` turns it off) and records the rule on each record\n  ([[observation:reset-records-out-of-order]]).\n- **Samples placed by count.** Every sample is kept and placed by its count, so none are lost to the stamp\n  arithmetic ([[observation:snuadc-stamp-arithmetic]], [[observation:loaders-lose-samples]]).\n- **Corrected scale factors** for the Primus waveforms and the mislabelled arterial tracks\n  ([[observation:primus-waveform-gains]], [[observation:art-with-pleth-factors]]).\n- **Whole-file time zero** ([[observation:loaded-subset-time-zero]]).\n- **Numeric delay labels** ([[observation:numerics-not-front-stamped]]).\n\n[[software:vitaldb-bench]] checks what it reads against the source files. Changes to the reader made during this\nwork may be ahead of the public repository until they are merged.\n",
      "parts": [
        {
          "group": "The VitalDB reader",
          "path": "packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/connector.py",
          "summary": "Reads .vital files into TimeNet records; import options, including the reset reordering."
        },
        {
          "group": "The VitalDB reader",
          "path": "packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/resets.py",
          "summary": "Finds the reset clusters in an SNUADC track and puts their records back in sampling order."
        },
        {
          "group": "The VitalDB reader",
          "path": "packages/timenet-connectors/src/timenet_connectors/datasets/vitaldb/vitaldb/tests/test_resets.py",
          "summary": "Tests of the reordering rule, including the variant clusters."
        }
      ],
      "related": [
        "vitaldb-bench"
      ],
      "references": [
        "timenet"
      ],
      "derived": {
        "studies": [
          "reset-cluster-order"
        ],
        "observations": [
          "primus-waveform-gains",
          "reset-records-out-of-order",
          "snuadc-stamp-arithmetic"
        ],
        "mentioned_in": [
          "variable:snuadc-art",
          "variable:snuadc-ecg-ii",
          "variable:snuadc-pleth",
          "study:emr-rounding",
          "study:reset-cluster-order",
          "study:stamp-arithmetic",
          "study:time-zeros",
          "study:vitaldb-loader",
          "observation:art-with-pleth-factors",
          "observation:loaded-subset-time-zero",
          "observation:loaders-lose-samples",
          "observation:numerics-not-front-stamped",
          "observation:primus-waveform-gains",
          "observation:reset-records-out-of-order",
          "observation:snuadc-overlapping-records",
          "observation:snuadc-stamp-arithmetic",
          "observation:solar-st-duplicated",
          "software:vitaldb-bench"
        ]
      }
    },
    "vitaldb-bench": {
      "id": "vitaldb-bench",
      "name": "vitaldb-bench",
      "summary": "The timing and data-quality audit of VitalDB: the scripts behind most studies in this atlas, the clock model and corrected-time reader, the per-case labels, and resumable TimeNet builds with their fidelity checks.",
      "repo": "https://github.com/metrology-atlas/vitaldb-bench",
      "branch": "main",
      "visibility": "public",
      "local": "../../../vitaldb/vitaldb-bench",
      "licence": "MIT",
      "language": "Python 3.11+",
      "description": "vitaldb-bench measures how far every time stamp in the VitalDB release can be trusted, and builds databases\nthat carry what was found as labels. It has two halves.\n\n- **The installed package** (`vitaldb_bench`) builds TimeNet databases from the .vital files in short,\n  resumable chunks, reads tracks back out, checks every stored value and time against the source files, and\n  benchmarks the builds. It also holds the **clock model** and a **corrected-time reader**: a view of a build\n  in which every SNUADC and BIS sample carries its corrected time and an error distribution.\n- **The analysis scripts** (`tools/`) produced the tables and figures of the timing audit. Each reads the\n  .vital files directly, writes per-case output that stays on the analysis machine, and then aggregates it.\n  Only aggregates and figures are published (`results/`).\n\nIt depends on [[software:timenet]] for builds; its scripts also use [[software:physiosync]] (timing\ncorrections) and [[software:lineaccess]] (pressure-line accesses).\n\nNo VitalDB data are in the repository: only code, aggregate results and figures. To reproduce a result,\ndownload the release from PhysioNet and point the scripts' `--root` at it.\n",
      "role": "Most studies in this atlas were run with its scripts, and the **Code** section of each study names them.\nSeveral atlas pages come straight from it:\n\n- The [[method:delays-and-windows|timing budget]] behind the synchronisation planner is the model in\n  [[code:vitaldb-bench/tools/pair_timing_budget.py]]: each source's offset range and resolution become one\n  standard deviation, and the planner's coherence limit, grid and window sizing follow from it.\n- The accuracy-against-resolution chart on the home page began as\n  [[code:vitaldb-bench/tools/time_source_accuracy_figure.py]]; the atlas now draws it from its own data.\n- The clock fits that the [[study:snuadc-stamp-sawtooth]] study validated are made by\n  [[code:vitaldb-bench/tools/clock_model_fit.py]] and read through\n  [[code:vitaldb-bench/vitaldb_bench/correct/|the correct package]].\n- The time of day of each case ([[study:case-clock]]) is recovered by\n  [[code:vitaldb-bench/vitaldb_bench/case_clock.py]].\n\nThe atlas is meant to become the single source of these numbers: the plan is for the package and scripts to\nread the atlas JSON rather than keep their own copies.\n",
      "install": "```bash\npip install -e .            # the package: numpy, timenet, timenet-connectors\npip install -e \".[tools]\"   # plus the analysis scripts' dependencies (scipy, pandas, physiosync, lineaccess, ...)\npytest -q\n```\n\nEach script's docstring gives its commands and says what it reads and writes;\n`python tools/<script>.py --help` lists its options.\n",
      "parts": [
        {
          "group": "The package",
          "path": "vitaldb_bench/build.py",
          "summary": "Resumable TimeNet builds in time-boxed chunks, with a choice of how samples are placed in time."
        },
        {
          "group": "The package",
          "path": "vitaldb_bench/stores.py",
          "summary": "Reads a track of a case back out of a build, in a common shape for comparison."
        },
        {
          "group": "The package",
          "path": "vitaldb_bench/fidelity.py",
          "summary": "Checks every stored track of every built case against the shared reader, value by value and time by time. With the reset reordering on, the reference is reordered the same way."
        },
        {
          "group": "The package",
          "path": "vitaldb_bench/perf.py",
          "summary": "Size, read-speed and downstream-equivalence benchmarks of two builds of the same cases."
        },
        {
          "group": "The package",
          "path": "vitaldb_bench/clock_model.py",
          "summary": "The clock model: per-track fit tables written beside a build, and a corrected view of a TimeNet build. Each fit records the sample order it was made on."
        },
        {
          "group": "The package",
          "path": "vitaldb_bench/correct/",
          "summary": "Timing correction applied when data are read: clock models (`clock.py`), error distributions (`dist.py`) and the reader `CorrectedStore` (`layer.py`), which refuses a fit made on a different sample order."
        },
        {
          "group": "The package",
          "path": "vitaldb_bench/case_clock.py",
          "summary": "Recovers each case's clock time of day; the date stays hidden."
        },
        {
          "group": "The package",
          "path": "vitaldb_bench/label_families.py",
          "summary": "The derived label families (delay groups, line accesses, lab draws, BIS smoothing ...): where each family's rows come from and what its labels mean."
        },
        {
          "group": "The package",
          "path": "vitaldb_bench/timenet_labels.py",
          "summary": "Writes the label families into a TimeNet build as record annotations."
        },
        {
          "group": "The package",
          "path": "vitaldb_bench/timing_report.py",
          "summary": "Per-track summary of the rates a resync-timed build estimated (aggregates only)."
        },
        {
          "group": "Scripts not tied to one study",
          "path": "tools/pair_timing_budget.py",
          "summary": "Timing budget for combining two or more time sources: the model the atlas planner implements. Reads no data."
        },
        {
          "group": "Scripts not tied to one study",
          "path": "tools/time_source_accuracy_figure.py",
          "summary": "The original accuracy-against-resolution figure for every kind of time stamp."
        },
        {
          "group": "Scripts not tied to one study",
          "path": "tools/timestamp_resolution.py",
          "summary": "Temporal resolution of every kind of time stamp (aggregates only)."
        },
        {
          "group": "Scripts not tied to one study",
          "path": "tools/device_map_figure.py",
          "summary": "The device and timing-relationship diagram used as the system overview."
        },
        {
          "group": "Scripts not tied to one study",
          "path": "tools/sync_figures.py",
          "summary": "Redraws the clock re-synchronisation figures of the drift document from cached per-case rows."
        },
        {
          "group": "Scripts not tied to one study",
          "path": "tools/gen_tools_page.py",
          "summary": "Writes the scripts reference page from each script's docstring."
        },
        {
          "group": "Folders and documents",
          "path": "tools/",
          "summary": "All analysis scripts (about 90). Short provenance helpers are in `tools/adhoc/`."
        },
        {
          "group": "Folders and documents",
          "path": "results/",
          "summary": "Aggregate results and figures only; no per-case data."
        },
        {
          "group": "Folders and documents",
          "path": "scripts/",
          "summary": "Shell drivers for time-boxed build chunks."
        },
        {
          "group": "Folders and documents",
          "path": "docs/reference/tools.md",
          "name": "Scripts reference",
          "summary": "Every script in `tools/` with what it does."
        },
        {
          "group": "Folders and documents",
          "path": "docs/how-to/reproduce-results.md",
          "name": "Reproduce the results",
          "summary": "Which script made which file in `results/`, and the two-step pattern (per-case pass, then aggregate)."
        },
        {
          "group": "Folders and documents",
          "path": "docs/how-to/corrected-times.md",
          "name": "Corrected times",
          "summary": "Reading a build with corrected times and their uncertainty."
        },
        {
          "group": "Folders and documents",
          "path": "docs/how-to/timing-budget.md",
          "name": "Timing budget",
          "summary": "Planning a multi-signal analysis (the text behind the planner)."
        },
        {
          "group": "Folders and documents",
          "path": "docs/concepts/availability.md",
          "name": "Availability",
          "summary": "The availability rule: no value may be used before it could have been known."
        },
        {
          "group": "Folders and documents",
          "path": "docs/concepts/timing-modes.md",
          "name": "Timing modes",
          "summary": "How builds place samples in time (`nominal`, `file`, `resync`) and what each costs."
        },
        {
          "group": "Folders and documents",
          "path": "docs/timing_guide/vitaldb_timing_guide.pdf",
          "name": "VitalDB timing guide (PDF)",
          "summary": "The longer write-up of the timing audit."
        }
      ],
      "related": [
        "timenet",
        "physiosync",
        "lineaccess",
        "vitaldb-resolution"
      ],
      "references": [
        "harris2020numpy",
        "virtanen2020scipy",
        "mckinney2010pandas",
        "pandas-zenodo",
        "hunter2007matplotlib",
        "apache-arrow",
        "raasveldt2019duckdb",
        "makowski2021neurokit2",
        "bota2024biosppy",
        "cairosvg",
        "pytest"
      ],
      "derived": {
        "studies": [
          "airway-heartbeat-pilot",
          "arterial-line-accesses",
          "bis-cardiac-artefact",
          "bis-index-delay",
          "case-clock",
          "clock-correction-scan",
          "continuity-gaps-segments",
          "correction-periods",
          "device-correction-schedules",
          "drift-units",
          "drug-effect-latency",
          "emr-rounding",
          "hr-delay-groups",
          "lab-timestamp-meaning",
          "loop-closure",
          "numeric-clock-check",
          "numeric-delay-variability",
          "numeric-derivation-pilot",
          "paced-ecg",
          "pat-offset-census",
          "ppg-art-reference",
          "ppg-frame-clock",
          "ppg-sawtooth",
          "primus-pause-steps",
          "rare-device-sync",
          "record-messages-and-steps",
          "recorder-pc-steps",
          "reset-cluster-order",
          "setup-identification",
          "shared-bursts",
          "snuadc-stamp-sawtooth",
          "solar-snuadc-clock",
          "spo2-latency",
          "step-classification",
          "track-value-resolution",
          "ventilator-breath-matching"
        ],
        "observations": [
          "duplicated-records",
          "interleaved-records",
          "primus-awp-pauses",
          "primus-waveform-gains",
          "release-time-shift",
          "reset-records-out-of-order",
          "snuadc-overlapping-records"
        ],
        "examples": [
          "cuff-vs-arterial",
          "fluid-balance",
          "gas-gradients-at-the-draw",
          "hypotension-burden",
          "induction-hypotension",
          "oliguria-labels",
          "pat-vs-bp",
          "ppv-by-breath",
          "pulse-arrival-time",
          "regression-with-rounding",
          "urine-output-per-kg-per-hour"
        ],
        "mentioned_in": [
          "study:case-clock",
          "study:device-correction-schedules",
          "study:loop-closure",
          "study:primus-pause-steps",
          "study:reset-cluster-order",
          "study:setup-identification",
          "study:snuadc-stamp-sawtooth",
          "study:stamp-arithmetic",
          "study:ventilator-breath-matching",
          "observation:release-time-shift",
          "example:pulse-arrival-time",
          "software:analysis-workspace",
          "software:physiosync",
          "software:timenet"
        ]
      }
    },
    "vitaldb-python": {
      "id": "vitaldb-python",
      "name": "vitaldb (Python package)",
      "summary": "The VitalDB project's own Python reader for .vital files, as published on PyPI.",
      "url": "https://pypi.org/project/vitaldb/",
      "visibility": "public",
      "language": "Python",
      "description": "The reader most users load VitalDB with. The [[study:vitaldb-loader]] study read its source (version 1.7.2,\n`vitaldb/utils.py`) to see how it places samples in time. Code paths on this page refer to that version of the\npackage; there is no repository to link to, so they are shown as text.\n",
      "references": [
        "vitaldb-python"
      ],
      "derived": {
        "studies": [
          "vitaldb-loader"
        ],
        "mentioned_in": [
          "study:vitaldb-loader"
        ]
      }
    },
    "vitaldb-resolution": {
      "id": "vitaldb-resolution",
      "name": "vitaldb-resolution",
      "summary": "Every recorded VitalDB time and value as a probability distribution of the true value: native resolution, or a mixture of resolutions recovered from heaped data.",
      "repo": "https://github.com/metrology-atlas/vitaldb-resolution",
      "branch": "main",
      "visibility": "public",
      "local": "../../../vitaldb/vitaldb-resolution",
      "licence": "MIT",
      "language": "Python (numpy, scipy)",
      "description": "Models are JSON files evaluated by one generic engine. For timing, it describes the resolution of each kind of\ntime stamp: the second axis of the accuracy-against-resolution chart.\n",
      "related": [
        "vitaldb-bench"
      ],
      "references": [
        "harris2020numpy",
        "virtanen2020scipy",
        "mckinney2010pandas",
        "jax2018github",
        "pytest"
      ],
      "derived": {
        "studies": [
          "value-model-aneend",
          "value-model-anestart",
          "value-model-bmi",
          "value-model-height",
          "value-model-intraop-crystalloid",
          "value-model-intraop-ebl",
          "value-model-intraop-ppf",
          "value-model-intraop-uo",
          "value-model-opend",
          "value-model-opstart",
          "value-model-weight"
        ],
        "mentioned_in": [
          "method:value-resolution"
        ]
      }
    }
  },
  "references": {
    "allen2000variability": {
      "id": "allen2000variability",
      "group": "Pulse arrival time",
      "citation": "Allen J, Murray A. Variability of photoplethysmography peripheral pulse measurements at the ears, thumbs and toes. IEE Proc Sci Meas Technol. 2000;147(6):403-407.",
      "doi": "10.1049/ip-smt:20000846"
    },
    "allen2002age": {
      "id": "allen2002age",
      "group": "Pulse arrival time",
      "citation": "Allen J, Murray A. Age-related changes in peripheral pulse timing characteristics at the ears, fingers and toes. J Hum Hypertens. 2002;16(10):711-717.",
      "doi": "10.1038/sj.jhh.1001478"
    },
    "apache-arrow": {
      "id": "apache-arrow",
      "group": "Software",
      "citation": "Apache Software Foundation. Apache Arrow (PyArrow).",
      "url": "https://arrow.apache.org/"
    },
    "block2020conventional": {
      "id": "block2020conventional",
      "group": "Pulse arrival time",
      "citation": "Block RC, Yavarimanesh M, Natarajan K, et al. Conventional pulse transit times as markers of blood pressure changes in humans. Sci Rep. 2020;10:16373.",
      "doi": "10.1038/s41598-020-73143-8"
    },
    "bota2024biosppy": {
      "id": "bota2024biosppy",
      "group": "Software",
      "citation": "Bota P, Silva R, Carreiras C, Fred A, Plácido da Silva H. BioSPPy: a Python toolbox for physiological signal processing. SoftwareX. 2024;26:101712.",
      "doi": "10.1016/j.softx.2024.101712"
    },
    "cairosvg": {
      "id": "cairosvg",
      "group": "Software",
      "citation": "Kozea / CourtBouillon. CairoSVG.",
      "url": "https://github.com/Kozea/CairoSVG"
    },
    "carroll2006measurement": {
      "id": "carroll2006measurement",
      "group": "Metrology",
      "citation": "Carroll RJ, Ruppert D, Stefanski LA, Crainiceanu CM. Measurement Error in Nonlinear Models. A Modern Perspective. 2nd ed. Boca Raton; Chapman and Hall/CRC; 2006.",
      "doi": "10.1201/9781420010138"
    },
    "cole2006mime": {
      "id": "cole2006mime",
      "group": "Metrology",
      "citation": "Cole SR, Chu H, Greenland S. Multiple-imputation for measurement-error correction. Int J Epidemiol. 2006;35(4):1074-1081.",
      "doi": "10.1093/ije/dyl097"
    },
    "eytan2019revisiting": {
      "id": "eytan2019revisiting",
      "group": "The authors' earlier work",
      "citation": "Eytan D, Mazwi ML, Goodwin AJ, Goodfellow SD, Assadi A, Greer RW, Jegatheeswaran A, Laussen PC. Revisiting oxygen dissociation curves and bedside measured arterial saturation in critically ill children. Intensive Care Med. 2019;45(12):1832-1834.",
      "doi": "10.1007/s00134-019-05792-x",
      "note": "Pulse oximetry against arterial blood gas saturation at the bedside."
    },
    "goldberger2000physionet": {
      "id": "goldberger2000physionet",
      "group": "Data and recording",
      "citation": "Goldberger AL, Amaral LAN, Glass L, Hausdorff JM, Ivanov PCh, Mark RG, Mietus JE, Moody GB, Peng C-K, Stanley HE. PhysioBank, PhysioToolkit, and PhysioNet: components of a new research resource for complex physiologic signals. Circulation. 2000;101(23):e215-e220.",
      "doi": "10.1161/01.CIR.101.23.e215"
    },
    "goodwin2020practical": {
      "id": "goodwin2020practical",
      "group": "The authors' earlier work",
      "citation": "Goodwin AJ, Eytan D, Greer RW, Mazwi M, Thommandram A, Goodfellow SD, Assadi A, Jegatheeswaran A, Laussen PC. A practical approach to storage and retrieval of high-frequency physiological signals. Physiol Meas. 2020;41(3):035008.",
      "doi": "10.1088/1361-6579/ab7cb5",
      "note": "Earlier work on storing and retrieving high-frequency physiological signals."
    },
    "goodwin2021blood": {
      "id": "goodwin2021blood",
      "group": "The authors' earlier work",
      "citation": "Goodwin A, Mazwi ML, Somer J, Schwartz SM, McEwan A, Eytan D. Blood pressure in critically ill children: exploratory analyses of concurrent invasive and noninvasive measurements. Crit Care Explor. 2021;3(12):e0586.",
      "note": "Value accuracy of non-invasive against invasive blood pressure."
    },
    "goodwin2022timing": {
      "id": "goodwin2022timing",
      "group": "The authors' earlier work",
      "citation": "Goodwin AJ, Eytan D, Dixon W, Goodfellow SD, Doherty Z, Greer RW, McEwan A, Tracy M, Laussen PC, Assadi A, et al. Timing errors and temporal uncertainty in clinical databases – a narrative review. Front Digit Health. 2022;4:932599."
    },
    "goodwin2023": {
      "id": "goodwin2023",
      "group": "The authors' earlier work",
      "citation": "Goodwin AJ, Dixon W, Mazwi M, Hahn CD, Meir T, Goodfellow SD, Kazazian V, Greer RW, McEwan A, Laussen PC, et al. The truth Hertz – synchronization of electroencephalogram signals with physiological waveforms recorded in an intensive care unit. Physiol Meas. 2023;44(8):085002."
    },
    "goodwin2023high": {
      "id": "goodwin2023high",
      "group": "The authors' earlier work",
      "citation": "Goodwin AJ. High frequency physiological data quality modelling in the intensive care unit [PhD thesis]. Sydney: School of Biomedical Engineering, The University of Sydney; 2023."
    },
    "harris2020numpy": {
      "id": "harris2020numpy",
      "group": "Software",
      "citation": "Harris CR, Millman KJ, van der Walt SJ, et al. Array programming with NumPy. Nature. 2020;585(7825):357-362.",
      "doi": "10.1038/s41586-020-2649-2"
    },
    "healey2011diurnal": {
      "id": "healey2011diurnal",
      "group": "Anthropometry",
      "citation": "Healey EL, Burden AM, McEwan IM, Fowler NE. Diurnal variation in stature: do those with chronic low-back pain differ from asymptomatic controls? Clin Biomech. 2011;26(4):331-336.",
      "doi": "10.1016/j.clinbiomech.2010.11.017"
    },
    "heitjan1989grouped": {
      "id": "heitjan1989grouped",
      "group": "Coarse data and heaping",
      "citation": "Heitjan DF. Inference from grouped continuous data: a review. Stat Sci. 1989;4(2):164-179.",
      "doi": "10.1214/ss/1177012601"
    },
    "heitjan1991coarse": {
      "id": "heitjan1991coarse",
      "group": "Coarse data and heaping",
      "citation": "Heitjan DF, Rubin DB. Ignorability and coarse data. Ann Stat. 1991;19(4):2244-2253.",
      "doi": "10.1214/aos/1176348396"
    },
    "hrvmatch": {
      "id": "hrvmatch",
      "group": "Software",
      "citation": "LaussenLabs. hrvmatch [source code]. GitHub.",
      "url": "https://github.com/LaussenLabs/hrvmatch",
      "note": "Beat matching by inter-beat intervals; cite with the commit used."
    },
    "hunter2007matplotlib": {
      "id": "hunter2007matplotlib",
      "group": "Software",
      "citation": "Hunter JD. Matplotlib: a 2D graphics environment. Comput Sci Eng. 2007;9(3):90-95.",
      "doi": "10.1109/MCSE.2007.55"
    },
    "jax2018github": {
      "id": "jax2018github",
      "group": "Software",
      "citation": "Bradbury J, Frostig R, Hawkins P, Johnson MJ, Katariya Y, Leary C, Maclaurin D, Necula G, Paszke A, VanderPlas J, Wanderman-Milne S, Zhang Q. JAX: composable transformations of Python+NumPy programs. 2018.",
      "url": "https://github.com/jax-ml/jax",
      "note": "Used to fit the value models; cite with the version used."
    },
    "jcgm-100-2008": {
      "id": "jcgm-100-2008",
      "group": "Metrology",
      "citation": "JCGM 100:2008. Evaluation of measurement data – Guide to the expression of uncertainty in measurement (GUM). Joint Committee for Guides in Metrology; 2008.",
      "doi": "10.59161/JCGM100-2008E",
      "url": "https://www.bipm.org/en/publications/guides/gum.html"
    },
    "jcgm-101-2008": {
      "id": "jcgm-101-2008",
      "group": "Metrology",
      "citation": "JCGM 101:2008. Evaluation of measurement data – Supplement 1 to the 'Guide to the expression of uncertainty in measurement' – Propagation of distributions using a Monte Carlo method. Joint Committee for Guides in Metrology; 2008.",
      "doi": "10.59161/JCGM101-2008"
    },
    "jcgm-102-2011": {
      "id": "jcgm-102-2011",
      "group": "Metrology",
      "citation": "JCGM 102:2011. Evaluation of measurement data – Supplement 2 to the 'Guide to the expression of uncertainty in measurement' – Extension to any number of output quantities. Joint Committee for Guides in Metrology; 2011.",
      "doi": "10.59161/JCGM102-2011"
    },
    "jcgm-200-2012": {
      "id": "jcgm-200-2012",
      "group": "Metrology",
      "citation": "JCGM 200:2012. International vocabulary of metrology – Basic and general concepts and associated terms (VIM). Joint Committee for Guides in Metrology; 2012.",
      "doi": "10.59161/JCGM200-2012",
      "url": "https://jcgm.bipm.org/vim/en"
    },
    "jcgm-gum-6-2020": {
      "id": "jcgm-gum-6-2020",
      "group": "Metrology",
      "citation": "JCGM GUM-6:2020. Guide to the expression of uncertainty in measurement – Part 6: Developing and using measurement models. Joint Committee for Guides in Metrology; 2020.",
      "doi": "10.59161/JCGMGUM-6-2020"
    },
    "jose2025exploring": {
      "id": "jose2025exploring",
      "group": "Pulse arrival time",
      "citation": "Jose AS, Srivastav S, Mehta B. Exploring the repeatability of pulse arrival time in healthy subjects: a test-retest approach. Indian J Physiol Pharmacol. 2025;69(1):27-33.",
      "doi": "10.25259/IJPP_149_2024"
    },
    "kdigo2012aki": {
      "id": "kdigo2012aki",
      "group": "Data and recording",
      "citation": "Kidney Disease Improving Global Outcomes (KDIGO) Acute Kidney Injury Work Group. KDIGO clinical practice guideline for acute kidney injury. Kidney Int Suppl. 2012;2(1):1-138.",
      "doi": "10.1038/kisup.2012.1"
    },
    "khosla1964bodyweight": {
      "id": "khosla1964bodyweight",
      "group": "Anthropometry",
      "citation": "Khosla T, Billewicz WZ. Measurement of change in body-weight. Br J Nutr. 1964;18(1):227-239.",
      "doi": "10.1079/BJN19640022"
    },
    "kortekaas2018small": {
      "id": "kortekaas2018small",
      "group": "Pulse arrival time",
      "citation": "Kortekaas MC, van Velzen MHN, Grüne F, Niehof SP, Stolker RJ, Huygen FJPM. Small intra-individual variability of the pre-ejection period justifies the use of pulse transit time as approximation of the vascular transit. PLoS ONE. 2018;13(10):e0204105.",
      "doi": "10.1371/journal.pone.0204105"
    },
    "leatt1986spinal": {
      "id": "leatt1986spinal",
      "group": "Anthropometry",
      "citation": "Leatt P, Reilly T, Troup JG. Spinal loading during circuit weight-training and running. Br J Sports Med. 1986;20(3):119-124.",
      "doi": "10.1136/bjsm.20.3.119"
    },
    "lee2018vitalrecorder": {
      "id": "lee2018vitalrecorder",
      "group": "Data and recording",
      "citation": "Lee H-C, Jung C-W. Vital Recorder – a free research tool for automatic recording of high-resolution time-synchronised physiological data from multiple anaesthesia devices. Sci Rep. 2018;8:1527.",
      "doi": "10.1038/s41598-018-20062-4"
    },
    "lee2019analysis": {
      "id": "lee2019analysis",
      "group": "Pulse arrival time",
      "citation": "Lee J, Yang S, Lee S, Kim HC. Analysis of pulse arrival time as an indicator of blood pressure in a large surgical biosignal database: recommendations for developing ubiquitous blood pressure monitoring methods. J Clin Med. 2019;8(11):1773.",
      "doi": "10.3390/jcm8111773"
    },
    "lee2022vitaldb": {
      "id": "lee2022vitaldb",
      "group": "Data and recording",
      "citation": "Lee H-C, Park Y, Yoon SB, Yang SM, Park D, Jung C-W. VitalDB, a high-fidelity multi-parameter vital signs database in surgical patients. Sci Data. 2022;9:279.",
      "doi": "10.1038/s41597-022-01411-5"
    },
    "lee2022vitaldb-physionet": {
      "id": "lee2022vitaldb-physionet",
      "group": "Data and recording",
      "citation": "Lee H-C, Jung C-W. VitalDB, a high-fidelity multi-parameter vital signs database in surgical patients (version 1.0.0). PhysioNet. 2022.",
      "doi": "10.13026/czw8-9p62",
      "url": "https://physionet.org/content/vitaldb/1.0.0/",
      "note": "The release this atlas describes."
    },
    "liao2023filtering": {
      "id": "liao2023filtering",
      "group": "Pulse arrival time",
      "citation": "Liao S, Liu H, Lin W-H, Zheng D, Chen F. Filtering-induced changes of pulse transmit time across different ages: a neglected concern in photoplethysmography-based cuffless blood pressure measurement. Front Physiol. 2023;14:1172150.",
      "doi": "10.3389/fphys.2023.1172150"
    },
    "makowski2021neurokit2": {
      "id": "makowski2021neurokit2",
      "group": "Software",
      "citation": "Makowski D, Pham T, Lau ZJ, Brammer JC, Lespinasse F, Pham H, Schölzel C, Chen SHA. NeuroKit2: a Python toolbox for neurophysiological signal processing. Behav Res Methods. 2021;53(4):1689-1696.",
      "doi": "10.3758/s13428-020-01516-y"
    },
    "martin2016weighing": {
      "id": "martin2016weighing",
      "group": "Pulse arrival time",
      "citation": "Martin SL-O, Carek AM, Kim C-S, Ashouri H, Inan OT, Hahn J-O, Mukkamala R. Weighing scale-based pulse transit time is a superior marker of blood pressure than conventional pulse arrival time. Sci Rep. 2016;6:39273.",
      "doi": "10.1038/srep39273"
    },
    "mckinney2010pandas": {
      "id": "mckinney2010pandas",
      "group": "Software",
      "citation": "McKinney W. Data structures for statistical computing in Python. In: Proceedings of the 9th Python in Science Conference. 2010. p. 56-61.",
      "doi": "10.25080/Majora-92bf1922-00a"
    },
    "michard2000ppv": {
      "id": "michard2000ppv",
      "group": "Pulse arrival time",
      "citation": "Michard F, Boussat S, Chemla D, Anguel N, Mercat A, Lecarpentier Y, Richard C, Pinsky MR, Teboul JL. Relation between respiratory changes in arterial pulse pressure and fluid responsiveness in septic patients with acute circulatory failure. Am J Respir Crit Care Med. 2000;162(1):134-138.",
      "doi": "10.1164/ajrccm.162.1.9903035"
    },
    "mohammadpoor2024use": {
      "id": "mohammadpoor2024use",
      "group": "Pulse arrival time",
      "citation": "Mohammadpoor Faskhodi M, Garcia-Gonzalez MA, Fernandez-Chimeno M, et al. On the use of fractional calculus to improve the pulse arrival time (PAT) detection when using photoplethysmography (PPG) and electrocardiography (ECG) signals. PLoS ONE. 2024;19(2):e0298354.",
      "doi": "10.1371/journal.pone.0298354"
    },
    "obata2017noninvasive": {
      "id": "obata2017noninvasive",
      "group": "Pulse arrival time",
      "citation": "Obata Y, Ong QJ, Magruder JT, et al. Noninvasive assessment of the effect of position and exercise on pulse arrival to peripheral vascular beds in healthy volunteers. Front Physiol. 2017;8:47.",
      "doi": "10.3389/fphys.2017.00047"
    },
    "openeeg": {
      "id": "openeeg",
      "group": "Timing and synchronisation",
      "citation": "VitalDB. openeeg: open-source EEG processing for depth of anaesthesia (BIS-mimic algorithms validated against VitalDB). GitHub.",
      "url": "https://github.com/vitaldb/openeeg",
      "note": "Commit bd476c5, 2 June 2026; read October 2026."
    },
    "pandas-zenodo": {
      "id": "pandas-zenodo",
      "group": "Software",
      "citation": "The pandas development team. pandas-dev/pandas: Pandas. Zenodo.",
      "doi": "10.5281/zenodo.3509134",
      "note": "Cite the record for the version used, with McKinney 2010."
    },
    "pilge2006timedelay": {
      "id": "pilge2006timedelay",
      "group": "Timing and synchronisation",
      "citation": "Pilge S, Zanner R, Schneider G, Blum J, Kreuzer M, Kochs EF. Time delay of index calculation: analysis of cerebral state, bispectral, and narcotrend indices. Anesthesiology. 2006;104(3):488-494.",
      "doi": "10.1097/00000542-200603000-00016"
    },
    "pollard2026physionet": {
      "id": "pollard2026physionet",
      "group": "Data and recording",
      "citation": "Pollard T, Moody BE, Lehman L, Gow B, Fernandes C, Xie C, Johnson A, Mark RG, Heldt T. PhysioNet as a global platform for biomedical research. Nature Health. 2026.",
      "doi": "10.1038/s44360-026-00096-z",
      "note": "The standard PhysioNet citation now given on the VitalDB page; volume and pages to be confirmed."
    },
    "pytest": {
      "id": "pytest",
      "group": "Software",
      "citation": "Krekel H, Oliveira B, Pfannschmidt R, Bruynooghe F, Laugher B, Bruhin F. pytest. 2004.",
      "url": "https://github.com/pytest-dev/pytest",
      "note": "Cite with the major.minor version used."
    },
    "pyyaml": {
      "id": "pyyaml",
      "group": "Software",
      "citation": "Simonov K, et al. PyYAML.",
      "url": "https://github.com/yaml/pyyaml"
    },
    "raasveldt2019duckdb": {
      "id": "raasveldt2019duckdb",
      "group": "Software",
      "citation": "Raasveldt M, Mühleisen H. DuckDB: an embeddable analytical database. In: Proceedings of the 2019 International Conference on Management of Data (SIGMOD '19). ACM; 2019. p. 1981-1984.",
      "doi": "10.1145/3299869.3320212"
    },
    "rampil1998primer": {
      "id": "rampil1998primer",
      "group": "Timing and synchronisation",
      "citation": "Rampil IJ. A primer for EEG signal processing in anesthesia. Anesthesiology. 1998;89(4):980-1002.",
      "doi": "10.1097/00000542-199810000-00023"
    },
    "rfc5905": {
      "id": "rfc5905",
      "group": "Timing and synchronisation",
      "citation": "Mills D, Martin J, Burbank J, Kasch W. Network Time Protocol Version 4: Protocol and Algorithms Specification. RFC 5905. RFC Editor; 2010.",
      "doi": "10.17487/RFC5905",
      "url": "https://www.rfc-editor.org/info/rfc5905",
      "note": "How a PC clock is usually kept on UTC; the recorder PCs' link to UTC is assumed, not observed."
    },
    "rietveld2002rounding": {
      "id": "rietveld2002rounding",
      "group": "Coarse data and heaping",
      "citation": "Rietveld P. Rounding of arrival and departure times in travel surveys: an interpretation in terms of scheduled activities. J Transp Stat. 2002;5(1):71-81.",
      "url": "https://www.bts.gov/archive/publications/journal_of_transportation_and_statistics/volume_05_number_01/paper_05/index",
      "note": "Reported clock times heap on multiples of 5, 15, 30 and 60 minutes; rounding probabilities are fitted by maximum likelihood and Bayes' rule gives the distribution of the true time given the reported one. Also a Tinbergen Institute discussion paper (TI 01-110/3, 2001)."
    },
    "rubin1987multiple": {
      "id": "rubin1987multiple",
      "group": "Metrology",
      "citation": "Rubin DB. Multiple Imputation for Nonresponse in Surveys. New York; Wiley; 1987.",
      "doi": "10.1002/9780470316696"
    },
    "ruffolo2025high": {
      "id": "ruffolo2025high",
      "group": "The authors' earlier work",
      "citation": "Ruffolo I, Siddiqui A, Nguyen B, Dixon W, Assadi A, Greer R, Schwartz S, Brudno M, Mariakakis A, Goodwin A. High-fidelity measurement of pulse arrival time in critically ill children using standard bedside monitoring equipment. Physiol Meas. 2025;46(11):115006."
    },
    "snuadc": {
      "id": "snuadc",
      "group": "Data and recording",
      "citation": "VitalDB. SNUADC: firmware and hardware of the VitalDB analogue-to-digital converter. GitHub.",
      "url": "https://github.com/vitaldb/snuadc",
      "note": "Firmware firmware/SNUADC/SNUADC.ino, read October 2026."
    },
    "timenet": {
      "id": "timenet",
      "group": "Software",
      "citation": "Maritsch M, Stoffregen T, Kaar T, et al. TimeNet: an extensible unified data infrastructure for next-generation temporal foundation models. arXiv:2610.04407. 2026.",
      "url": "https://github.com/OpenTSLM/TimeNet",
      "note": "Preprint details to be confirmed."
    },
    "vanvelzen2017increasing": {
      "id": "vanvelzen2017increasing",
      "group": "Pulse arrival time",
      "citation": "van Velzen MHN, Loeve AJ, Niehof SP, Mik EG. Increasing accuracy of pulse transit time measurements by automated elimination of distorted photoplethysmography waves. Med Biol Eng Comput. 2017;55(11):1989-2000.",
      "doi": "10.1007/s11517-017-1642-x"
    },
    "virtanen2020scipy": {
      "id": "virtanen2020scipy",
      "group": "Software",
      "citation": "Virtanen P, Gommers R, Oliphant TE, et al. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nat Methods. 2020;17(3):261-272.",
      "doi": "10.1038/s41592-019-0686-2"
    },
    "vitaldb-docs-config": {
      "id": "vitaldb-docs-config",
      "group": "Data and recording",
      "citation": "VitalDB. VitalRecorder configuration file (vr.conf) guide. VitalDB documentation. Accessed 19 October 2026.",
      "url": "https://vitaldb.net/docs/?documentId=VitalRecorder%2FConfiguration_Guide.md",
      "note": "Current VitalRecorder (1.18.x); the release was recorded with earlier versions."
    },
    "vitaldb-docs-devices": {
      "id": "vitaldb-docs-devices",
      "group": "Data and recording",
      "citation": "VitalDB. VitalRecorder supported devices. VitalDB documentation. Accessed 19 October 2026.",
      "url": "https://vitaldb.net/docs/?documentId=VitalRecorder%2FSupported_Devices.md",
      "note": "Current VitalRecorder (1.18.x); the release was recorded with earlier versions."
    },
    "vitaldb-docs-fileformat": {
      "id": "vitaldb-docs-fileformat",
      "group": "Data and recording",
      "citation": "VitalDB. Vital file format specification (format version 3). VitalDB documentation. Accessed 19 October 2026.",
      "url": "https://vitaldb.net/docs/?documentId=VitalRecorder%2FVital_File_Format.md"
    },
    "vitaldb-docs-manual": {
      "id": "vitaldb-docs-manual",
      "group": "Data and recording",
      "citation": "VitalDB. VitalRecorder user manual. VitalDB documentation. Accessed 19 October 2026.",
      "url": "https://vitaldb.net/docs/?documentId=VitalRecorder%2FUser_Manual.md",
      "note": "Current VitalRecorder (1.18.x); the release was recorded with earlier versions."
    },
    "vitaldb-python": {
      "id": "vitaldb-python",
      "group": "Software",
      "citation": "VitalLab. vitaldb [Python package]. PyPI.",
      "url": "https://pypi.org/project/vitaldb/",
      "note": "Version 1.7.2 was read for the loader study."
    },
    "vivanti2013weight": {
      "id": "vivanti2013weight",
      "group": "Anthropometry",
      "citation": "Vivanti A, Yu L, Palmer M, Dakin L, Sun J, Campbell K. Short-term body weight fluctuations in older well-hydrated hospitalised patients. J Hum Nutr Diet. 2013;26(5):429-435.",
      "doi": "10.1111/jhn.12034"
    },
    "voss1997diurnal": {
      "id": "voss1997diurnal",
      "group": "Anthropometry",
      "citation": "Voss LD, Bailey BJR. Diurnal variation in stature: is stretching the answer? Arch Dis Child. 1997;77(4):319-322.",
      "doi": "10.1136/adc.77.4.319"
    },
    "wang2008heaping": {
      "id": "wang2008heaping",
      "group": "Coarse data and heaping",
      "citation": "Wang H, Heitjan DF. Modeling heaping in self-reported cigarette counts. Stat Med. 2008;27(19):3789-3804.",
      "doi": "10.1002/sim.3281"
    },
    "youn2014changes": {
      "id": "youn2014changes",
      "group": "Pulse arrival time",
      "citation": "Youn AM, Shin YS, Park SI. Changes in pulse transit time according to target controlled infusion of propofol versus sevoflurane inhalation induction. Anesth Pain Med. 2014;9(1):48-53.",
      "note": "Cited for the change after induction only."
    },
    "zensical": {
      "id": "zensical",
      "group": "Software",
      "citation": "Zensical. Zensical: static site generator.",
      "url": "https://zensical.org/"
    }
  }
}
