VeloMetrixby Generable

VeloMetrix data formatter

How PK/PD source files become analysis tables, and what the formatter does at the six places data preparation usually goes wrong.

← Back to VeloMetrix

  • Agent annotates
  • Library transforms
  • Checks flag

The approach

Four steps · the agent annotates, the library does the rest

You upload

Source files + documents

  • dm.csv
  • ex.xpt
  • pc.csv
  • lb.sas7bdat
  • protocol.pdf

Files stay as delivered. Nothing edits them.

1 · An agent annotates

Writes a configuration

pk:
source: pc.csv
value: PCSTRESN
unit: PCSTRESU
time: PCDTC
lloq: 5 ng/mL

Which file feeds which table, which column plays which role, units, identifier patterns, rules. It can read the files. It cannot change a value or produce a table. When the data and documents cannot answer, it asks you.

2 · A library transforms

Configuration + files → tables

  • subjects
  • dosing
  • PK
  • PD
  • labs
  • covariates

Deterministic: the same inputs give the same tables on every run. Every step that changes data is written to a record of transformations with row counts. No guessing: what it cannot parse becomes a stated failure or a row marked not eligible, with a reason.

3 · A library of checks

Flags, row by row

valueeligiblereason
0.246yes
—nobelow LLOQ

Named, versioned checks run once on the whole formatted dataset and raise issues on a subject, a record or a field. A failing row is kept and marked, so a reviewer can find it. Twenty-one checks of the configuration against the data already run while the agent works.

4 · Export

Canonical and custom formats

  • canonical
  • per time basis
  • custom preset

Every study leaves in the same canonical layout: the same tables, columns and units, one set per time basis. A named output preset states what a house format needs, such as a NONMEM-ready file, and the configuration is checked against it before any data is read.

The agent never transforms.

Its only output is the configuration, which you can read line by line.

The library never guesses.

Unknown unit, unknown dosing label, unplaceable time: failure or a flagged row.

Answers are kept.

Each question and answer goes to an append-only log beside the configuration.

Rows are kept.

Eligibility for modeling is a flag with a reason, before and after quality checks.

Subject identifiers

Matching · merges · updates

What goes wrong

  • each file writes the ID differently
  • leading zeros lost in Excel
  • raw-string join drops subjects silently
  • later delivery re-sends rows
  • concentrations with no demographics row
  • Two patterns for one column that yield different parts: the configuration is refused at load.
  • Date-stamped deliveries of one file are one logical file: earliest is the base, later ones append in order, each with its own column mapping.
  • A delivery can instead replace the earlier data, or update named fields by key. An update that matches nothing is an error.
  • A subject in a data file but not in the subjects table: an error while the agent works; at format time its rows are removed and reported.

You seeSubjects removed, row count, source files, and the two remedies: supply the demographics, or exclude the subject explicitly.

fileas writtenpattern
dm.csvABC-101-0007{study}-{site:03d}-{subject:04d}
pc.csv1010007{site:03d}{subject:04d}
ex.csv101-007{site:03d}-{subject:03d}
lb.xlsxSITE 101 · SUBJ 7 (integers)two columns joined, then {site:d}-{subject:d}
study template {site:03d}-{subject:04d}→canonical ID 101-0007→stable subject key

Leading zeros, separators and study prefixes are surface form. The parts are read as numbers, so 007, 0007 and 7 are one subject. All joins use the key, never the raw strings.

keyvalue
0007·D1·2h0.494
+
keyvalue
0007·D14·2h0.681
0007·D1·2h0.510
→

stops · key 0007·D1·2h is in both files

The row key here is subject · day · time point. The April file re-sends the Day 1, 2 h sample for subject 0007 with a different value, so the run stops and lists that key instead of keeping both rows or picking one.

Units

pooling cohorts · one unit per quantity

What goes wrong

  • cohorts pooled, each lab in its own unit
  • ng/mL, µg/L and mg/L for one analyte
  • unit column ignored
  • mg/kg read as mg
  • rate per minute vs per hour
  • percent typed as proportion
  • The unit comes from a declared constant or from the file's own unit column, per row, so cohorts with different units pool without a manual step.
  • PK concentration is rescaled to mg/L, the mass unit of the dose (mg).
  • Value, LLOQ and ULOQ are scaled together.
  • PD markers have no universal unit: each has a declared unit, and a row in a different unit with no declared exact conversion is kept and marked not eligible.
  • Lab units convert where a conversion is declared and never decide eligibility.
  • Warning when a declared constant contradicts the unit column on some rows, and when a percent-valued marker typed as a proportion has values above 100.

You seeThe canonical unit and how many rows were converted from each source unit. A reason on each row that failed its unit rule. A question when the unit is not in the data at all.

cohortdoseconcentrationLLOQ
A · single dose, lab 1100 mg840 ng/mL5 ng/mL
B · multiple dose, lab 2100 mg0.84 µg/mL0.01 µg/mL
C · microdose, lab 3250 µg2.1 µg/L0.05 µg/L
↓concentration takes the dose's mass unit: mg and mg/L
cohortdose (mg)conc. (mg/L)LLOQ (mg/L)eligible
A1000.840.005yes
B1000.840.01yes
C0.250.00210.00005yes

Each cohort keeps its own LLOQ, scaled with its values. With dose in mg and concentration in mg/L, clearance and volume come out in L/h and L with no hidden factor of 1000.

reportedformatted
conc. 7 U7 (as reported)kept · not eligible · unit not parsed
dose 2 mg/kg2 mg/kgkept · not eligible
rate 10 mg/min600 mg/ha rate must state its unit
rate 10 (no unit)never assumed per hour

Time basis

one clock per subject · zero at first dose · four bases side by side

Every dose and sample can carry an observed time (when it happened) and anominal time (when the protocol planned it). Studies rarely record both for both. The formatter puts every recorded time on one clock per subject, sets zero at that subject's first dose, and publishes up to four bases. Each row is eligible or not on each basis, with a reason.

  • dose, observed
  • dose, planned only
  • sample, observed
  • sample, planned only
recorded asabsolutepublished
2026-03-04T08:3003-04 08:3048.5 h
study day 3, 14:3003-04 14:3054.5 h
26 h since randomisation03-02 10:002.0 h
basissample time fromdose time from
observedrecordedrecorded
nominalplannedplanned
nominal offsetsplanned offsetrecorded
pre-dose anchoredrecordedpre-dose PK draw

Doses observed, PK nominal only

The dosing file has clock times. The PK file has only labels such as "2 H post-dose".

dosePK08:121 H2 H4 H08:12 + 2 h = 10:12

Planned offsets are added to each subject's observed dose time, so a late dose moves its samples with it.

  • observed ✗
  • nominal offsets ✓
  • nominal ✓

PK observed, doses on a schedule

Samples have clock times. Doses have a date and a regimen, no clock.

dosePKdate only07:5509:58pre-dose→ 2.05 h

The dose clock is taken from that day's pre-dose PK draw. Sample times stay as recorded.

  • observed ✗
  • pre-dose anchored ✓
  • nominal, if planned

Observed and nominal both recorded

Each row has an actual date-time and a planned label. They disagree by minutes.

dosePK2 H · 2.18 h4 H · 3.92 h

Both are kept, in separate columns, so the deviation stays visible. The recommended basis is the one with the most PK-eligible subjects.

  • observed ✓
  • nominal ✓

Observed, but the clock time is missing

A sample is recorded with a date and no time of day.

dosePKday 3, no hourhours = missing · day kept

Hours are published as missing and the day is kept; it is not read as midnight. Row reason: "its time is known only to the day".

  • observed ✗ this row
  • nominal, if planned

First dose has no time

Later doses are timed, the first is not. Time zero is unknown.

dosePK0 h = ?never re-centred on a later dose

The subject gets no observed times at all. Zero is not moved to the second dose to make the numbers work.

  • observed ✗ whole subject
  • others: own inputs

Unscheduled sample

An extra draw between planned visits, often forced onto the nearest planned time.

dosePK1 H8 H5.3 hno planned time

Rows declared unscheduled get no planned time. Nothing is invented for them, and they stay usable at their real time.

  • observed ✓
  • nominal ✗ this row

Exports are always in the canonical format, one set of tables per basis.

You seePer table and basis: whether the time annotation is complete, how many rows and subjects lack a time, and which input is missing. A reason on each affected row. A recommended basis: the one with the most PK-eligible subjects.

Covariates

one value per subject · typed

What goes wrong

  • two rows, different weights, one picked arbitrarily
  • time-varying value collapsed silently
  • category coded 1/2 treated as a number
  • The covariate table is time-invariant. A quantity that changes over time belongs in labs, which keeps its time course.
  • A column mapped as a covariate that varies within a subject is an error during authoring, with the two choices: move it to labs, or declare how to reduce it.
  • A value that violates its type stays in the table, marked not eligible.
  • If the disagreement includes the unit, no numeric summary is offered.

You seeThe question, e.g. "2 subjects have more than one row and the rows disagree on weight". The method applied, with row counts before and after.

subjectweightunit
101-000772.0kg
101-000772.0kg
101-001281.5kg
101-001284.0kg
→
0007: identical rows collapse to one0012: rows disagree · format stopsasks: keep first · keep last · stop and fix the source

The stop message names the table, the columns that disagree and the number of subjects. It carries no data values. The agent may not choose.

typeexampleviolates
continuousage 54
positive continuousweight 72.0−72.0
countprior lines 22.5
categoricalrace: declared setvalue outside the set
binarysex F / Mthird value

Dose expansion

courses → one row per administration

What goes wrong

  • last dose dropped, or one too many
  • holds and reductions ignored
  • overlapping records double a dose
  • tau inferred from gaps
  • Built-in cadences: QD, BID, TID, QID, QOD, weekly and multi-week forms, "every N hours", single administration. A study can add its own label.
  • A cap on doses per course stops a mistyped interval from producing a runaway expansion.
  • Tau comes from the cadence label on the dose that opens the interval, then the study's declared interval, then a stated dose-group interval. Otherwise it is missing. It is never inferred from dose spacing.

You seeThe expansion decision in the record of transformations, the overlap warning, "zero dose" on hold rows, and one question, asked once, when a multi-dose study states no interval anywhere.

amountcadencestartend
100 mgBIDDay 1 09:00Day 7 09:00
start + k × 12 h, end included → 13 doses024487296120144 hhours since first dose · each row flagged as expanded, with an identifier derived from the course record
source saysformatter emits
hold, or amount 0zero-dose rows at their slots · not eligible · later doses unaffected
reduction to 50 mgexpands at the amount actually given
two records, same time, different amountslater-starting record wins + warning (or stop, if the study says so)
cadence, no end dateleft unexpanded, recorded
label "Q36H-ALT" not knownformat fails, naming the label
dose never recordedstays a gap · nothing is synthesised

Derived labs

a fixed catalogue of named formulas · plus BLQ handling

What goes wrong

  • eGFR from a spreadsheet, equation unknown
  • µmol/L fed to a mg/dL formula
  • sex coded 1/2, mapping assumed
  • age held at baseline for two years
  • Catalogue: body surface area (Mosteller); eGFR by CKD-EPI 2021, MDRD 4-variable, CKD-EPI 2009; eGFR de-indexed to the individual's BSA in mL/min. Free-form formulas are not accepted.
  • Marker arithmetic: albumin-corrected calcium, sum of longest diameters, geometric mean over replicates.
  • Creatinine in any unit other than mg/dL or µmol/L is refused, naming the covariate and the unit.
  • A sex value that is not recognisably female or male gives a missing result with a counted warning. Never a guess.
  • A derived marker is named apart from any lab-reported eGFR; a colliding name is refused.

You seeThe inputs, the formula and, for the longitudinal form, the pairing rule. Missing results where an input was missing or ambiguous, with a count.

inputreportedused
creatinine88.4 µmol/L1.0 mg/dL (÷ 88.4)
age9389 (capped)
sexFfemale
→CKD-EPI 2021→eGFR · mL/min/1.73 m²
heightweight
170 cm70 kg
→Mosteller √(h·w / 3600)→BSA 1.82 m²
formgivesstored as
baselineone value per subject, from baseline inputscovariate
longitudinalone value per creatinine occasion, age advanced, no carry-forwardits own lab marker

← Back to VeloMetrix