Chapter 3
How a trial, a subject and a visit are written down in WIA-BIO-010, and how the same facts travel through the three CDISC layers — collection, tabulation and analysis — without losing their meaning on the way.
Chapter 2 treated the trial as a calculation: how many subjects, how much power, which test. This chapter treats it as a record. A trial produces hundreds of thousands of individual facts — a blood pressure taken at 09:40 on a Tuesday, a glucose value reported by a central laboratory in its own units, a note that the subject was fasting — and every one of them must survive a long journey. It is written on a case report form, typed or transmitted into an electronic data capture (EDC) system, reorganised into submission tables, derived into analysis variables and finally summarised in the tables, listings and figures that a regulator reads. If a unit is dropped or an identifier is ambiguous at any point, the fact is damaged, and the damage usually surfaces months later, when it is expensive to repair.
WIA-BIO-010 addresses this journey on two levels. Section 7 of the specification defines three JSON objects — trial registration, patient data and adverse event — and the reference TypeScript package @wia/bio-010 defines the types behind them. Section 3 of the specification then points to the CDISC family of standards (CDASH, SDTM and ADaM) as the formats in which the same data are collected, tabulated and analysed. The specification gives the outline of each layer but does not spell out the mapping between them. This chapter supplies that mapping, marks the places where the JSON examples, the TypeScript types and CDISC practice disagree, and closes with a complete walk-through for one subject. Safety data (the adverse event object, MedDRA and CTCAE) gets its own chapter; here it appears only as a preview.
Specification §7 is short: three JSON examples, each built around one scenario, a Phase III study of a fictional "Drug X" in type 2 diabetes with HbA1c as the primary endpoint. Read together, they describe what a WIA-BIO-010 implementation must be able to store and exchange at minimum. They are examples, not schemas: the specification publishes no JSON Schema and no list of required fields. The TypeScript types in section 3.2 are therefore the closest thing to a formal definition, and the two are not identical.
The registration object identifies the study and states what it intends to prove. The annotated version below repeats the specification example; the comments are this book's, and JSON itself does not allow comments, so they must be removed before the object is parsed.
{
"trialId": "NCT12345678", // registry identifier; see note below
"title": "Phase III Study of Drug X in Type 2 Diabetes",
"phase": "III", // 'I' | 'II' | 'III' | 'IV'
"indication": "Type 2 Diabetes Mellitus",
"sponsor": "Pharma Company Inc.",
"principalInvestigator": {
"name": "Dr. Jane Smith",
"institution": "University Medical Center"
},
"primaryEndpoint": {
"parameter": "HbA1c", // WHAT is measured
"metric": "Change from baseline",// HOW it is summarised per subject
"timepoint": "Week 52" // WHEN it is assessed
},
"sampleSize": {
"planned": 600, // from the protocol's sample-size section
"enrolled": 0, // running counters, updated by the system
"randomized": 0,
"completed": 0
},
"startDate": "2025-01-15",
"estimatedCompletionDate": "2026-12-31",
"status": "recruiting"
}
Three design choices deserve attention. First, the primary endpoint is split into parameter, metric and timepoint. This is more useful than a free-text endpoint, because each part has a downstream counterpart: the parameter becomes a laboratory test code in SDTM and an analysis parameter (PARAMCD) in ADaM, the metric becomes a derived variable such as CHG, and the timepoint becomes an analysis visit (AVISIT). An endpoint written as "HbA1c improvement" cannot be traced this way; one written as three fields can. What the triple does not capture is how intercurrent events such as rescue medication or treatment discontinuation are handled — the "estimand" questions discussed in Chapters 2 and 8 — so the protocol text remains authoritative.
Second, sampleSize mixes a planned constant with three running counters. The planned figure comes from the protocol and should never change without a protocol amendment; the counters are computed from subject records. Storing both in one object is convenient for dashboards, but an implementation should derive the counters from the subject table rather than increment them by hand, otherwise the two will drift apart.
Third, trialId is shown as an NCT number. NCT numbers are assigned by ClinicalTrials.gov when a study is registered there; a sponsor cannot mint one. Many trials also carry a sponsor protocol number, an EU trial number or a national registry number (in Korea, for example, a Clinical Research Information Service entry). The WIA object has room for only one identifier, so the recommendation of this book is to put the sponsor's stable study identifier in trialId — the same value that will become STUDYID in SDTM — and to keep registry numbers as additional attributes in the implementation's own extension fields.
The patient object bundles demographics, randomization and a list of visits for one subject.
{
"subjectId": "STUDY01-001",
"demographics": { "age": 58, "sex": "M", "race": "Asian",
"ethnicity": "Not Hispanic or Latino" },
"enrollmentDate": "2025-02-01",
"randomization": {
"date": "2025-02-01",
"treatment": "Drug X",
"stratification": {
"baseline_hba1c": "≥8.0%",
"diabetes_duration": ">10 years"
}
},
"visits": [
{
"visitId": "V1",
"visitName": "Baseline",
"visitDate": "2025-02-01",
"measurements": {
"hba1c": 8.5, // % (NGSP units)
"fasting_glucose": 165, // mg/dL
"weight": 85.2, // kg
"sbp": 132, // mmHg
"dbp": 84 // mmHg
}
}
]
}
The object reads naturally, and that is its purpose: it is how an investigator thinks about one subject. It is not, however, how the data will be tabulated. Several properties that matter later are implicit here. The units exist only in the reader's head (the comments above were added by this book). The measurements map puts laboratory values (HbA1c, glucose) next to vital signs (weight, blood pressure), although CDISC files them in different domains. Stratification factors are free-text labels ("≥8.0%") rather than coded categories, so the same stratum could appear as ">=8.0", "8.0 or more" or "high" in different sites' data. And the object says nothing about the site, the consent date or whether the subject was fasting — three facts that SDTM will ask for.
There is also a blinding question. In a double-blind trial the treatment field must not be visible to site staff or to anyone cleaning data. In practice the randomization list lives in an interactive response technology (IRT) system, and the actual treatment is joined to the clinical data only at unblinding. An implementation of the WIA object must therefore either leave treatment empty until unblinding or restrict read access to it; storing it openly in the EDC would compromise the study.
The third object records one adverse event for one subject: a verbatim term, its MedDRA coding, start and end dates, severity, a block of seriousness criteria, a causality assessment, the action taken and the outcome. Its structure follows the SDTM AE domain closely, which makes its mapping easier than that of the patient object. The details — why severity and seriousness are separate concepts, how MedDRA coding works, how grades and reporting clocks are applied — are the subject of Chapter 4. For this chapter it is enough to note that the AE object carries its own subjectId, so it can be stored and exchanged independently of the patient object, and that the same identifier rules apply to it.
The package @wia/bio-010 (file api/typescript/src/types.ts) turns the examples into interfaces. The relevant ones for this chapter are shown in abbreviated form below; optional fields end with ?.
type TrialPhase = 'I' | 'II' | 'III' | 'IV';
type TrialStatus = 'planned' | 'recruiting' | 'active'
| 'suspended' | 'completed' | 'terminated';
interface ClinicalTrial {
id: string; // note: "trialId" in the spec JSON
title: string; phase: TrialPhase; indication: string; sponsor: string;
principalInvestigator: Investigator; // { name, institution, email?, credentials? }
primaryEndpoint: Endpoint; // { parameter, metric, timepoint, target?, unit? }
secondaryEndpoints?: Endpoint[];
sampleSize: SampleSizeInfo; // { planned, enrolled, randomized, completed,
// withdrawn?, dropoutRate? }
startDate: Date | string; estimatedCompletionDate: Date | string;
status: TrialStatus; protocolVersion?: string;
registrationDate: Date | string; // required, absent from the spec example
duration?: number; // weeks
}
interface PatientEnrollment {
subjectId: string; trialId: string;
demographics: Demographics; // age, sex 'M'|'F'|'Other', race, ethnicity,
// weight?, height?, bmi?
enrollmentDate: Date | string;
consentDate: Date | string; // required
randomization?: Randomization; // date, treatment, randomizationNumber?,
// stratification?: Record<string,string>, arm?
status: 'enrolled' | 'randomized' | 'active' | 'withdrawn' | 'completed';
}
interface PatientVisit {
visitId: string; visitName: string; visitDate: Date | string;
subjectId: string;
visitWindow?: number; // SDK: "Visit window (days from baseline)"
visitDay?: number; // SDK: "Actual visit day"
status: 'scheduled' | 'completed' | 'missed' | 'unscheduled';
measurements?: ClinicalMeasurements; // hba1c?, fasting_glucose?, weight?,
// [key: string]: number | undefined
laboratoryResults?: LabResults; // hematology, chemistry, lipids, [key]: any
vitalSigns?: VitalSigns; // systolicBP, diastolicBP, heartRate,
// temperature, respiratoryRate, oxygenSaturation
}
Where the specification examples and the types disagree, a conforming implementation has to choose. The table lists every difference relevant to storage and exchange, with the resolution this book recommends. None of these choices is mandated by v1.0; they are engineering decisions that should be written into the implementation's own data specification.
| Topic | Spec JSON (§7) | TypeScript type | Recommended handling |
|---|---|---|---|
| Trial identifier | trialId | ClinicalTrial.id | Accept both on input; emit one name consistently and document it. The value becomes STUDYID. |
| Registration date | not shown | registrationDate required | Required; store the date the record was created in the system. |
| Subject → trial link | implied by subjectId prefix | trialId required | Store explicitly; never parse identifiers to recover relationships. |
| Consent date | not shown | consentDate required | Required before any clinical data is accepted (see Chapter 7, error B006). |
| Subject status | not shown | status required | Required; transitions are covered in Chapter 6. |
| Visits | nested visits[] inside patient | separate PatientVisit with its own subjectId; no visits field on PatientEnrollment | Store visits as separate records keyed by subject; nest only in export views. |
| Visit status | not shown | status required | Required; a missed visit is data, not an absence of data. |
| Vital signs | sbp, dbp, weight inside measurements | dedicated vitalSigns object (no weight field) | Use vitalSigns for BP and HR; keep weight in measurements or demographics, but route it to VS on mapping. |
| Units | none | in comments only | Treat the commented units as the canonical units; reject values in other units at the API boundary or carry an explicit unit field. |
| Dates | ISO 8601 strings | Date | string | Exchange ISO 8601 strings only; a JavaScript Date serialises with a time and time zone that the source did not have. |
Two type-level details also matter for mapping. ClinicalMeasurements has an open index signature, so any key with a numeric value is accepted: {"hba1c": 8.5} and {"HbA1c": 8.5} are both valid and will be treated as different measurements. A mapper must therefore work from an explicit list of known keys and reject the rest, as the code in section 3.6 does. LabResults goes further with [key: string]: any, so its extra keys are not even required to be numbers. Its named chemistry block lists glucose, creatinine, BUN, ALT, AST and albumin but not HbA1c, although the SDK's validation code looks for chemistry.hba1c; Chapter 5 returns to this. For modelling purposes, the safe rule is: every measurement that will be submitted needs a defined key, a defined unit and a defined target domain, and an open map provides none of the three.
CDISC maintains a chain of standards that together cover the life of trial data. The figure shows where each layer sits and what it is for.
CDASH (Clinical Data Acquisition Standards Harmonization) standardises the questions asked on the case report form and the variable names used to store the answers. Specification §3.1 lists the core domains an implementation should support:
Why does the collection layer need its own standard when SDTM already exists? Because the question that makes sense to a nurse is often not the row a statistician needs. Four examples show the gap.
--DAT and --TIM). SDTM stores one ISO 8601 value in --DTC, which may be partial if only the month was known.--STAT = NOT DONE and a reason in --REASND, not a missing row.The WIA patient object sits at the boundary: it is shaped like collected data (one subject, visit by visit) but it is also the unit of exchange between systems. It is therefore reasonable to align its keys with CDASH names where a CDASH variable exists, and to document the mapping where it does not.
SDTM (Study Data Tabulation Model) is the format in which collected data are submitted to regulators such as the US FDA and Japan's PMDA. It organises data into domains, each a table in which every row is one observation. Specification §3.2 lists the general-purpose variables; the "--" prefix is replaced by the two-letter domain code (for example LBTESTCD, VSORRES).
| Variable | Meaning | Practical rule |
|---|---|---|
STUDYID | Study identifier | Identical in every domain of the study. |
DOMAIN | Two-letter domain code | DM, AE, VS, LB and so on. |
USUBJID | Unique subject identifier | Unique across all studies of the product in a submission, not only within one study. |
--SEQ | Sequence number | Unique per subject within a domain; used to reference a row from other datasets. |
--TESTCD / --TEST | Short code / name of the test | Take codes from CDISC Controlled Terminology, not from local habits. |
--ORRES / --ORRESU | Result and unit as originally received | Character; preserves text such as "<0.5" or "trace". |
--STRESC / --STRESU | Result and unit after standardisation | One unit per test across the study; numeric copy in --STRESN. |
The reference package reflects this model in two declarations. SDTMDomain is a union of ten domain codes: DM (demographics), AE (adverse events), VS (vital signs), LB (laboratory), CM (concomitant medications), EX (exposure), DS (disposition), MH (medical history), PE (physical examination) and QS (questionnaires). SDTMRecord declares STUDYID, DOMAIN, USUBJID and a field named SEQ, with an open index signature for everything else. Note that the SDTM variable is always prefixed (LBSEQ, VSSEQ); the SDK's bare SEQ is an in-memory convenience and must be renamed on export. The ten domains are a reasonable starting set for a drug trial but not a complete list — trial design domains (such as TA, TV and TS), subject visits (SV) and supplemental qualifiers (SUPP--) are also needed in a real submission.
Specification §3.2 illustrates SDTM with two LB rows. They are reproduced here with the LBSTRESN column added, because a numeric standardised result is what analysis programs actually read.
| STUDYID | DOMAIN | USUBJID | LBSEQ | LBTESTCD | LBORRES | LBORRESU | LBSTRESC | LBSTRESN | LBSTRESU |
|---|---|---|---|---|---|---|---|---|---|
| STUDY01 | LB | STUDY01-01 | 1 | HBA1C | 7.2 | % | 7.2 | 7.2 | % |
| STUDY01 | LB | STUDY01-01 | 2 | GLUC | 135 | mg/dL | 7.49 | 7.49 | mmol/L |
The glucose conversion uses the molar mass of glucose, 180.16 g/mol. One mmol/L equals 180.16 mg/L, which is 18.016 mg/dL, so 135 mg/dL ÷ 18.016 = 7.493 mmol/L, reported as 7.49. The factor is specific to glucose: cholesterol, creatinine and urea each have their own molar mass and therefore their own factor, and some tests (enzyme activities in U/L, for example) have no molar conversion at all. A mapping program should hold a conversion table keyed by test code and unit pair, and it should fail rather than guess when it meets a pair it does not know.
HbA1c illustrates a second kind of standardisation. The spec keeps it in percent (the NGSP/DCCT scale that is common in the United States and Korea). Many laboratories also report the IFCC unit, mmol/mol; the two are related by the published master equation IFCC = 10.929 × (NGSP − 2.15), so a baseline of 8.5 % corresponds to about 69 mmol/mol. The choice of standard unit is a study-level decision that belongs in the statistical analysis plan; what matters is that one unit is chosen per test and that --ORRES keeps what was actually received.
Two smaller points in the example are worth fixing before copying it. The example's USUBJID is "STUDY01-01", while the §7.2 patient is "STUDY01-001"; in the walk-through below the longer form is used throughout. And the test code HBA1C is illustrative: CDISC Controlled Terminology defines its own code for glycated haemoglobin, and a real study should take the code from the terminology release it has pinned rather than from an example.
SDTM describes what was observed; ADaM (Analysis Data Model) describes what will be analysed. The distinction matters because the analysis needs values that were never observed directly: a baseline chosen by rule, a change from that baseline, a flag saying which record counts at Week 52, a population flag saying whether the subject belongs to the intention-to-treat set. ADaM datasets are built from SDTM with fully documented derivations, so that a reviewer can trace every analysed number back to its source rows. Specification §3.3 names three datasets.
TRT01P, TRT01A), population flags (ITTFL, SAFFL, PPROTFL), disposition and key baseline covariates, including the stratification factors used at randomization.TRTEMFL derived by comparing the event's start with the first dose. Chapter 4 covers it.The derived variables listed in the specification are the core of the BDS:
AVAL analysis value (numeric, in the standard unit)
BASE baseline value for this subject and parameter
CHG change from baseline = AVAL − BASE
PCHG percent change = 100 × (AVAL − BASE) / BASE
BNRIND baseline reference-range indicator (LOW / NORMAL / HIGH)
ANRIND analysis reference-range indicator (LOW / NORMAL / HIGH)
Three rules keep these derivations honest. Baseline must be defined in the analysis plan — commonly the last non-missing value on or before the first dose — and flagged on the record that supplies it (ABLFL = "Y"); it must not simply be "the visit called Baseline". PCHG is undefined when BASE is zero, and the program should leave it missing rather than divide. And reference-range indicators compare a value with the laboratory's normal range, which is a clinical classification, not a data-validity check; Chapter 7 separates the two carefully.
Comparing BNRIND with ANRIND produces a shift table: how many subjects moved from NORMAL at baseline to HIGH at Week 12, how many stayed NORMAL, and so on. Shift tables are a routine part of laboratory safety reporting, and they are only as reliable as the reference ranges attached to each laboratory result — another reason to transfer ranges with every central laboratory load.
Traceability runs in one direction and must be recorded. Each ADaM record should identify its SDTM source (for BDS datasets commonly through SRCDOM, SRCVAR and SRCSEQ, or through the --SEQ of the source row), and the dataset metadata, usually delivered as define.xml, should describe every derivation in words a reviewer can check. The specification cites SDTM 1.7 and ADaM 1.1 as its reference versions; CDISC has published later implementation guides since, and a study should state which versions it follows rather than assume.
This section follows subject STUDY01-001 from the §7.2 JSON through SDTM to one ADLB change-from-baseline value. The baseline data are those of the specification. The Week 12 values are invented for illustration and are labelled as such. For simplicity the study reference start date (RFSTDTC, normally the first dose) is assumed to be the randomization date, 2025-02-01.
| WIA JSON field | SDTM target | Note |
|---|---|---|
trialId / id | STUDYID (all domains) | Sponsor study identifier, not a registry number. |
subjectId | USUBJID; site-level part to SUBJID | Must be unique across the whole submission. |
| — | DM.SITEID | Required in DM but absent from the WIA object; add it. |
demographics.age | DM.AGE + AGEU = "YEARS" | Record the age derivation rule (at consent or at randomization). |
demographics.sex | DM.SEX | Type allows "Other"; SDTM terminology uses M, F, U and UNDIFFERENTIATED — define the mapping. |
demographics.race, ethnicity | DM.RACE, DM.ETHNIC | Map free text to controlled terms ("Asian" → ASIAN). |
consentDate | DM.RFICDTC | Informed consent date. |
randomization.treatment | DM.ARM/ARMCD; ACTARM/ACTARMCD | Planned vs actual arm; blinded until unblinding. |
randomization.stratification | ADSL stratum variables (and SUPPDM if needed) | Store coded levels, not labels. |
visits[].visitName, visitId | VISIT, VISITNUM | VISITNUM must be numeric and sortable. |
visits[].visitDate | --DTC; derive --DY | Study day has no day 0. |
measurements.hba1c, fasting_glucose | LB rows; LBFAST = "Y" for glucose | Key "fasting_glucose" encodes a condition; SDTM moves it to LBFAST. |
measurements.weight, sbp, dbp | VS rows: WEIGHT, SYSBP, DIABP | Vital signs, even though stored with labs in the JSON. |
The following TypeScript routes each known measurement key to its domain, test code and unit, converts glucose to mmol/L, derives the study day and assigns per-domain sequence numbers. Unknown keys raise an error instead of being silently dropped or guessed — the behaviour an auditor will expect.
import type { SDTMRecord } from '@wia/bio-010/types';
type Route = {
domain: 'LB' | 'VS'; testcd: string; test: string; unit: string;
fast?: 'Y';
std?: (v: number) => { n: number; u: string };
};
const GLUCOSE_MGDL_PER_MMOLL = 18.016; // 180.16 g/mol ÷ 10
const ROUTE: Record<string, Route> = {
hba1c: { domain: 'LB', testcd: 'HBA1C', test: 'Hemoglobin A1C', unit: '%' },
fasting_glucose: { domain: 'LB', testcd: 'GLUC', test: 'Glucose', unit: 'mg/dL', fast: 'Y',
std: v => ({ n: v / GLUCOSE_MGDL_PER_MMOLL, u: 'mmol/L' }) },
weight: { domain: 'VS', testcd: 'WEIGHT', test: 'Weight', unit: 'kg' },
sbp: { domain: 'VS', testcd: 'SYSBP', test: 'Systolic Blood Pressure', unit: 'mmHg' },
dbp: { domain: 'VS', testcd: 'DIABP', test: 'Diastolic Blood Pressure', unit: 'mmHg' },
};
// SDTM study day: day 1 is the reference start date; there is no day 0.
function studyDay(dtc: string, rfstdtc: string): number {
const d = (Date.parse(dtc) - Date.parse(rfstdtc)) / 86_400_000;
return d >= 0 ? d + 1 : d;
}
export function mapVisit(
studyId: string, usubjid: string, rfstdtc: string, visitNum: number,
visit: { visitName: string; visitDate: string; measurements?: Record<string, number> },
): SDTMRecord[] {
const seq = { LB: 0, VS: 0 };
const rows: SDTMRecord[] = [];
for (const [key, value] of Object.entries(visit.measurements ?? {})) {
const r = ROUTE[key];
if (!r) throw new Error(`No SDTM route for measurement "${key}"`);
const std = r.std ? r.std(value) : { n: value, u: r.unit };
const p = r.domain;
rows.push({
STUDYID: studyId, DOMAIN: p, USUBJID: usubjid, SEQ: ++seq[p], // rename to LBSEQ/VSSEQ on export
[`${p}TESTCD`]: r.testcd, [`${p}TEST`]: r.test,
[`${p}ORRES`]: String(value), [`${p}ORRESU`]: r.unit,
[`${p}STRESC`]: std.n.toFixed(2).replace(/\.?0+$/, ''),
[`${p}STRESN`]: std.n, [`${p}STRESU`]: std.u,
...(r.fast ? { LBFAST: r.fast } : {}),
VISITNUM: visitNum, VISIT: visit.visitName.toUpperCase(),
[`${p}DTC`]: visit.visitDate, [`${p}DY`]: studyDay(visit.visitDate, rfstdtc),
});
}
return rows;
}
const baseline = { visitName: 'Baseline', visitDate: '2025-02-01',
measurements: { hba1c: 8.5, fasting_glucose: 165, weight: 85.2, sbp: 132, dbp: 84 } };
for (const r of mapVisit('STUDY01', 'STUDY01-001', '2025-02-01', 1, baseline)) {
const p = r.DOMAIN;
console.log([p, r.SEQ, r[p + 'TESTCD'], r[p + 'ORRES'], r[p + 'ORRESU'],
r[p + 'STRESC'], r[p + 'STRESU'], r[p + 'DY']].join(' | '));
}
Expected output:
LB | 1 | HBA1C | 8.5 | % | 8.5 | % | 1
LB | 2 | GLUC | 165 | mg/dL | 9.16 | mmol/L | 1
VS | 1 | WEIGHT | 85.2 | kg | 85.2 | kg | 1
VS | 2 | SYSBP | 132 | mmHg | 132 | mmHg | 1
VS | 3 | DIABP | 84 | mmHg | 84 | mmHg | 1
Notice that LBSTRESC is rounded for display while LBSTRESN keeps full precision (9.1585…). Derivations downstream should use the numeric value; rounding twice is a classic source of small, hard-to-explain discrepancies between a sponsor's tables and a reviewer's recomputation. In a production mapper the sequence counters would also continue across visits rather than restart at 1 for each call, because --SEQ must be unique for the subject within the whole domain.
The DM record, built from the demographics, consent and randomization blocks, is one row (only selected variables are shown; SITEID "101" and ARMCD "DRUGX" are placeholders that the WIA object does not supply):
| USUBJID | SUBJID | SITEID | RFSTDTC | AGE | AGEU | SEX | RACE | ETHNIC | ARMCD | ARM |
|---|---|---|---|---|---|---|---|---|---|---|
| STUDY01-001 | 001 | 101 | 2025-02-01 | 58 | YEARS | M | ASIAN | NOT HISPANIC OR LATINO | DRUGX | Drug X |
The VS and LB rows are those printed by the mapper above, each carrying STUDYID = STUDY01, VISITNUM = 1, VISIT = BASELINE and the collection date. The LB row for glucose carries LBFAST = Y. Note what has happened to the JSON key fasting_glucose: the condition "fasting" has left the test name and become its own variable, so that a non-fasting glucose at another visit is the same test (GLUC) with a different qualifier, rather than a different, unrelated measurement.
Suppose (example values) that at the Week 12 visit, on 2025-04-26 (study day 85), the subject's HbA1c is 7.9 % and fasting glucose 142 mg/dL. The Week 12 LB rows are created by the same mapper with VISITNUM = 3, say. The ADLB records then look like this:
| USUBJID | PARAMCD | AVISIT | ADY | AVAL | ABLFL | BASE | CHG | PCHG |
|---|---|---|---|---|---|---|---|---|
| STUDY01-001 | HBA1C | Baseline | 1 | 8.5 | Y | 8.5 | ||
| STUDY01-001 | HBA1C | Week 12 | 85 | 7.9 | 8.5 | −0.6 | −7.06 | |
| STUDY01-001 | GLUC | Baseline | 1 | 9.1585 | Y | 9.1585 | ||
| STUDY01-001 | GLUC | Week 12 | 85 | 7.8819 | 9.1585 | −1.2766 | −13.94 |
The arithmetic can be checked by hand: CHG = 7.9 − 8.5 = −0.6 and PCHG = 100 × (−0.6) / 8.5 = −7.06 %. For glucose, 142 / 18.016 = 7.8819 mmol/L, CHG = 7.8819 − 9.1585 = −1.2766 mmol/L, and PCHG = −13.94 %, the same as (142 − 165) / 165 on the original scale, as it should be, because the conversion is a pure scale factor. Leaving CHG and PCHG empty on the baseline record is common practice (some sponsors fill CHG with 0; either convention is acceptable if documented). The primary endpoint of the §7.1 trial is at Week 52; the Week 52 record would be derived in exactly the same way, and the analysis would pick it through an analysis flag rather than by its visit label.
USUBJID must never repeat across studies pooled in a submission. The SDK's enrollPatient builds subject IDs as the trial ID plus a random three-digit number; with only 1,000 possible suffixes, the birthday-problem approximation √(2 × 1000 × ln 2) ≈ 37 shows that a collision becomes more likely than not after about 37 enrolments. Allocate subject numbers sequentially per site from a central counter and build USUBJID as study–site–subject.Date is serialised with toISOString(); if the date part is then cut from that string, the visit moves to the previous day and the study day shifts with it. Partial dates ("2025-02") are legal in SDTM and must not be padded with invented days; imputation, if any, belongs in ADaM with an imputation flag.fasting_glucose that the mapper assumes is mg/dL. Carry the unit with the value, or reject values from sources whose unit is not configured.--ORRES as received, including qualifiers such as "<" and text results. Replacing it with the standardised number erases evidence an inspector may ask for.BASE is.HbA1c, hba1c and a1c may coexist in measurements; an explicit routing table, as above, turns this silent inconsistency into a visible error.The following checks summarise what a WIA-BIO-010 data model should satisfy before its output is trusted downstream. They are this book's recommendations, derived from specification §3 and §7 and from CDISC practice; v1.0 itself does not state them as conformance requirements.
| # | Check | Basis |
|---|---|---|
| 1 | One documented name for the trial identifier; its value equals STUDYID. | §7.1, ClinicalTrial.id |
| 2 | Subject, consent date, status and site are stored explicitly for every subject. | PatientEnrollment, SDTM DM |
| 3 | Every measurement key has a defined unit and target domain; unknown keys are rejected. | §7.2, §3.1 |
| 4 | Dates are exchanged as ISO 8601 text; study days are derived, not typed. | SDTM --DTC, --DY |
| 5 | Original results are kept in --ORRES/--ORRESU; standard units are fixed per test. | §3.2 |
| 6 | Unit conversions are table-driven by test and unit pair and fail on unknown pairs. | §3.2 glucose example |
| 7 | Baseline, CHG and PCHG follow rules written in the analysis plan, with ABLFL set. | §3.3 |
| 8 | Every ADaM value traces to identifiable SDTM rows, and derivations are described in dataset metadata. | §3.3 |
| 9 | Treatment assignment is hidden from blinded roles until unblinding. | §7.2 randomization |
| 10 | The CDISC versions and controlled-terminology release used are recorded for the study. | §10.2 |
With the core records in place, the next chapter turns to the part of the data that carries the heaviest regulatory weight: adverse events, their coding in MedDRA, their grading and the clocks that govern their reporting.