Chapter 7

Chapter 7: Integration and Implementation — EDC, Part 11, Security and WIA Linkages

How the five components required by WIA-BIO-010 §8.1 fit together, what 21 CFR Part 11 asks of the system around them, how edit checks, consent and access control are enforced, and which of these duties the reference SDK leaves entirely to the implementer.

The previous chapters treated WIA-BIO-010 as a set of data shapes, statistical formulas and lifecycle rules. This chapter treats it as software that has to run at a hospital site on a Tuesday afternoon, when a study coordinator types a blood pressure into a form, a monitor later questions it, an investigator signs the page, and an inspector two years later asks who changed what and why. None of that behaviour lives in the JSON objects of §7. It lives in the system that stores those objects, and the standard says only a little about that system: §3.4 lists five EDC requirements (Part 11 compliance, audit trail, electronic signatures, encryption, role-based access) and four validation activities (range checks, logical checks, query management, data monitoring); §8.1 names five required components; §8.3 gives plausibility limits for nine vital-sign and laboratory values; §9.1 lists consent elements.

The reference package @wia/bio-010 is a stateless library. It creates objects, performs a numeric check and a handful of range warnings, and computes statistics. It has no database, no users, no audit log, no signature and no consent check. That is a reasonable design for a library, but it means that every requirement in this chapter is something the implementer builds around the SDK. The sections below say, for each requirement, what the regulation or the spec actually demands, what the SDK does today, and what a minimal correct implementation looks like. Where the book proposes a structure that v1.0 does not define, such as the audit record in section 7.2 or the consent record in section 7.4, it is labelled as a design proposal, not as part of the standard.

7.1 The five required components (§8.1)

Section 8.1 states that any compliant system must include a trial registration module, an EDC system with validation, safety reporting with SAE detection, a statistics engine, and a regulatory submission component producing CDISC exports and eCTD output. The list is short, and it is best read as a statement of functional coverage rather than as an architecture: a sponsor may buy an EDC, run safety in a separate pharmacovigilance database and do statistics in SAS or R, and still cover all five. What matters for conformance is that the five functions exist, that they exchange data in the WIA-BIO-010 shapes or in a documented mapping to them, and that the records they create meet the controls described in the rest of this chapter.

Component (§8.1)SDK entry pointsWhat the SDK providesWhat you must build around it
1. Trial registrationregisterTrialA ClinicalTrial object with defaults (52 weeks, 20 % dropout, status planned) and a placeholder NCT… IDPersistence, protocol version history, the real registry number, status transitions under change control
2. EDC with validationenrollPatient, collectPatientDataEnrollment object with a random 3-digit subject suffix; numeric check and a few advisory warningsConsent guard, uniqueness of subject IDs, §8.3 edit checks, query workflow, audit trail, signatures, access control
3. Safety reportingrecordAdverseEventAn AE ID, reportingRequired equal to the serious flag, and a deadline counted from onsetAwareness date, expectedness and causality logic (Chapter 4), case processing, submission tracking
4. Statistics enginecalculateSampleSize, calculatePower, performHypothesisTest, calculateDescriptiveStatsNormal-approximation formulas with fixed Z values (Chapter 5)Validated analysis environment, programs under version control, the statistical analysis plan
5. Regulatory submissiongenerateSubmissionA RegulatorySubmission record in draft status; no filesSDTM/ADaM dataset production, define.xml, eCTD assembly with external tools

A reference architecture

The diagram below shows one way to wire the SDK into the five components. The upper band is the application layer that users touch. The five components call the SDK where it helps. The lower band holds four shared services that the SDK does not supply and that every component depends on: identity and access control with electronic signatures, an append-only audit trail, a consent registry that enforces error code B006, and a persistent store with the scheduled jobs that turn operational data into SDTM and ADaM. The boundary between the bands is deliberate: the shared services are where Part 11 obligations are met, so they should be implemented once and reused, not re-implemented inside each component.

Site staff · investigators · monitors · data managers · safety physicians 1 Registration registerTrial 2 EDC enrollPatient collectPatientData 3 Safety recordAdverseEvent 4 Statistics calculate* performHypothesisTest 5 Submission generateSubmission (draft record only) Shared services — not provided by the SDK Identity, RBAC e-signatures Audit trail (append-only) Consent registry (B006 guard) Store + nightly SDTM/ADaM jobs SDTM · ADaM · define.xml → eCTD tools
Figure 7-1. The five §8.1 components call the SDK; the Part 11 controls live in shared services below them.

Two practical consequences follow from this picture. First, the SDK's ID generators should not be trusted as primary keys. registerTrial builds an NCT string from the last eight digits of the clock, and enrollPatient appends a random number between 000 and 999 to the trial ID, so two enrollments in the same trial collide with a probability that grows quickly with enrollment size. The store should assign identifiers and enforce uniqueness, and the registry number should come from the registry. Second, the SDK returns plain objects that callers can mutate freely. A component that receives a ClinicalTrial and changes status in memory has not made a controlled change; only the write to the store, accompanied by an audit record, counts.

7.2 21 CFR Part 11 in practice (§3.4)

21 CFR Part 11 is the US regulation on electronic records and electronic signatures. It applies to records that FDA's other regulations (the "predicate rules", such as the investigator recordkeeping requirements of 21 CFR 312.62) require to be kept, and to records submitted to FDA in electronic form. Subpart B sets controls for electronic records and Subpart C sets requirements for electronic signatures. FDA's guidance on the scope and application of Part 11 narrowed how the agency enforces some provisions, but the core controls described below are still what an inspector expects to see in a clinical data system, and European inspectors apply comparable expectations through EMA's guideline on computerised systems and electronic data in clinical trials. Spec §3.4 lists Part 11 compliance as the first EDC requirement without further detail, so this section spells out the parts that shape software design.

Audit trail

Section 11.10(e) requires secure, computer-generated, time-stamped audit trails that independently record the date and time of operator entries and actions that create, modify or delete electronic records. Changes must not obscure previously recorded information, and the audit trail must be retained at least as long as the records it covers and be available for agency review and copying. Four design rules follow directly. The trail is written by the system, not by the user; the timestamp comes from a trusted server clock, not from the browser. The previous value survives every change, so an "update" is never an overwrite of the only copy. The trail is independent of the data table, so deleting or editing a data row cannot remove its history. And the trail lives as long as the data, including after database lock and archiving.

The regulation speaks of date, time and operator actions; GCP adds the expectation that the reason for a change is recorded when it is not obvious, which in practice means that every post-entry correction carries a reason. The record below is a design proposal for one audit entry; v1.0 defines no audit format. It captures who (user, role, site), what (record, field, action, old and new value), when (UTC timestamp and the user's time zone for display), and why (reason text and the query that prompted the change). The prevHash/hash pair is optional: chaining each entry to its predecessor makes silent tampering with the stored trail detectable, which supports the "secure" requirement but is not itself demanded by the regulation.

{
  "auditId": "AUD-000184213",
  "trialId": "NCT12345678",
  "subjectId": "STUDY01-001",
  "recordType": "vitalSigns",
  "recordId": "DATA-1745629500000-k3v9x0a1q",
  "visit": "V3",
  "field": "systolicBP",
  "action": "update",
  "oldValue": 14.8,
  "newValue": 148,
  "reason": "Decimal slip at entry; source document shows 148 mmHg",
  "queryId": "Q-0042",
  "user": { "userId": "u-7731", "role": "site-coordinator", "siteId": "S014" },
  "timestampUtc": "2025-04-28T06:41:22.518Z",
  "clientTimeZone": "Asia/Seoul",
  "prevHash": "sha256:9f2c…e1",
  "hash": "sha256:4b7a…90"
}

Electronic signatures

Investigators sign CRFs to attest that the data are complete and accurate, and data managers and statisticians sign off on milestones such as database lock. Where these signatures are electronic, Subpart C applies. Each signature must be unique to one individual and never reused by or reassigned to anyone else (11.100). A signature not based on biometrics must use at least two distinct identification components, such as a user ID and a password; within one continuous session, the first signing uses all components and later signings may use at least one (11.200). The signed record must show the printed name of the signer, the date and time of signing and the meaning of the signature, for example "reviewed" or "approved" (11.50), and the signature must be linked to its record so that it cannot be excised, copied or transferred to falsify another record (11.70). Organizations also certify to FDA that their electronic signatures are intended as the legally binding equivalent of handwritten ones.

For implementers this means a signature is a record in its own right: a row that references the exact version of the signed content (a content hash is the simplest link), the signer, the meaning and the time. If the underlying data change after signing, the system must show the signature as applying to the earlier version and, by site procedure, prompt re-signature.

Access control and authority checks

Section 11.10 also requires limiting system access to authorized individuals (d), authority checks so that only authorized people can sign, alter records or perform operations (g), and controls over identification codes and passwords (11.300). In clinical systems the authority model is finer than "read" and "write": a monitor may raise a query but not edit the answer; a coder may assign a MedDRA term but not change the verbatim; a blinded statistician must never see the randomization list. Section 7.5 gives a role matrix.

System validation

Section 11.10(a) requires validation of systems to ensure accuracy, reliability, consistent intended performance and the ability to discern invalid or altered records. Validation is evidence that the configured system does what its requirements say, collected under a plan. For a WIA-BIO-010 implementation, the requirements include the §8.3 limits, the B001–B006 behaviours and the statistics formulas; Chapter 8 turns them into test vectors. ICH E6(R3) frames the effort as risk-proportionate: functions that affect subject safety or the reliability of primary endpoint data deserve the deepest testing.

What the SDK does not provide

None of this makes the SDK unusable. It makes it a calculation and shaping layer that must sit behind a server that owns identity, time, storage and history.

7.3 Edit checks and query management

An edit check is a rule evaluated when data are entered or changed, and a query is the discrepancy it raises for a person to resolve. Spec §3.4 names range checks, logical checks, query management and data monitoring; §8.3 supplies the only concrete numbers. Before using them it is essential to separate two kinds of range that the package mixes together.

Validity limits are not normal ranges

The §8.3 limits are plausibility limits: a systolic pressure below 60 mmHg or above 250 mmHg is so unusual in an ambulatory trial visit that it is more likely an entry error than a measurement, and it should be confirmed against the source. The constants VITAL_SIGNS_RANGES and LAB_RANGES in types.ts are normal reference ranges: values outside them are real and clinically interesting, and they drive flags such as LBNRIND or shift tables, not queries. A systolic pressure of 150 mmHg is outside the normal range and entirely valid. The table below sets the two side by side, with the SDK's actual behaviour.

Parameter§8.3 validity limit (query if outside)Normal range in SDK constants (clinical flag)What collectPatientData does
Systolic BP60–250 mmHg90–140 mmHgWarns if < 90 or > 250 (mixes both ranges); never an error
Diastolic BP40–150 mmHg60–90 mmHgNot checked
Heart rate30–200 bpm60–100 bpmWarns if < 30 or > 200
Temperature35–42 °C36.1–37.2 °CNot checked
Respiratory rate6–60 /min12–20 /minNot checked
Oxygen saturationnone95–100 %Not checked
HbA1c4.0–15.0 %4.0–5.6 % (target < 7.0)Warns if < 3 or > 20, read from chemistry.hba1c, a field the LabResults type does not declare
Glucose20–600 mg/dL70–100 mg/dL fastingWarns if < 20 or > 600
Creatinine0.3–15.0 mg/dL0.6–1.2 mg/dLNot checked
ALT / AST5–2000 U/LALT 7–56, AST 10–40 U/LWarns if > 200, labelled "Grade 2+" without reference to the upper limit of normal

Three further details matter. The SDK tests values with vitalSigns.systolicBP && …, so a value of 0, which is certainly an error, passes silently. All range findings are returned as warnings, and success stays true, so a caller that looks only at success accepts every out-of-range value. And the §7.2 patient JSON puts sbp and dbp inside the free-form measurements map, where the SDK only checks that they are numbers; the vital-sign rules fire only if data arrive in the typed vitalSigns object. An implementation should pick one location for each parameter and map the other into it before validation.

Hard and soft checks

A hard check blocks the save: the value is not a number, a required field is empty, a date is not a valid ISO 8601 date, a unit is missing. These correspond to error code B003 (validation failure). A soft check saves the value and opens a query: the value is outside the §8.3 limits, or a cross-field rule fails. These correspond to B007 (data out of range) or B003 for logical inconsistency. The reason the §8.3 limits should be soft is that the EDC must record what the source document says. A glucose above 600 mg/dL can occur in a diabetic emergency and a temperature below 35 °C in hypothermia; rejecting such a value forces the site to enter something that is not in the source, which is worse than an unusual number with a documented confirmation.

Logical and cross-form checks

Range checks look at one field. Most real discrepancies are relational. Typical checks for a trial modelled on WIA-BIO-010 include:

The query lifecycle

A query moves through a small state machine: open (raised by an edit check or manually by a monitor or data manager), answered (the site replies, correcting the value with a reason or confirming it as correct), and closed (the data manager accepts the answer), with re-opened when the answer is insufficient. Every transition is itself an audited event, and a correction made in response to a query carries the query ID in its audit record, as in the example in section 7.2. Before database lock, the usual criteria are that no query remains open, that required source data verification is complete, and that the investigator has signed the affected pages; the lock itself is a signed, audited event that makes further changes require a documented unlock.

7.4 Informed consent data (§9.1)

Consent is the legal and ethical precondition for collecting any trial data about a person. Spec §9.1 lists ten informed consent elements with reference to 21 CFR 50.25, and §8.4 defines B006, "Missing informed consent — cannot proceed with data collection". The spec's list is a useful checklist; it is worth knowing how it relates to the regulation text, because ethics committees and inspectors read the regulation.

Spec §9.1 itemWhere it sits in 21 CFR 50.25(a)
1–3. Purpose, expected duration, proceduresElement (1): statement that the study involves research, its purposes, the expected duration of participation, the procedures, and which procedures are experimental
4. Foreseeable risks or discomfortsElement (2)
5. Potential benefitsElement (3), benefits to the subject or others that may reasonably be expected
6. AlternativesElement (4), appropriate alternative procedures or courses of treatment
7. ConfidentialityElement (5), including that FDA may inspect the records
8. Compensation for injuryElement (6), for research involving more than minimal risk
9. Contact informationElement (7), whom to contact about the research, subjects' rights and research-related injury
10. Voluntary participationElement (8), refusal or withdrawal without penalty or loss of benefits

The regulation adds further elements that apply when appropriate, in 50.25(b), such as unforeseeable risks, circumstances in which participation may be terminated by the investigator, additional costs, consequences of withdrawal, and the commitment to share significant new findings; for applicable clinical trials, 50.25(c) requires a statement about the ClinicalTrials.gov registration. In Korea the equivalent requirements are set out in the Good Clinical Practice rules issued under the Pharmaceutical Affairs Act, and a Korean site will follow those; the data model does not change.

A consent record

PatientEnrollment holds a single consentDate. That is not enough to enforce consent over the life of a trial, because the ICF changes. When the protocol is amended or significant new safety information appears, the IRB approves a new ICF version and decides whether enrolled subjects must re-consent. The system therefore needs a consent history per subject: the ICF version, the date and time of signature, whether the subject or a legally authorized representative signed, optional consents (for example for future use of samples), and the date of any withdrawal. With that history, "does valid consent cover this data point?" becomes a question the system can answer for any collection date.

Enforcing B006 before any write

The guard below is a design proposal that throws ClinicalTrialError with code MISSING_CONSENT (B006) in four situations: no consent on file, data dated before the signature, data dated after withdrawal, and a signed version that differs from the version the IRB requires for that date. The caller resolves versionInForce from the approval history, because not every amendment requires re-consent and the required version depends on the collection date, not on today's date.

import { ClinicalTrialError, ClinicalTrialErrorCode } from '@wia/bio-010';

export interface ConsentRecord {
  subjectId: string;
  icfVersion: string;           // IRB-approved ICF version, e.g. "3.0"
  signedAt: string;             // ISO 8601 date-time of the signature
  signedBy: 'subject' | 'LAR';  // LAR = legally authorized representative
  withdrawnAt?: string;         // set when the subject withdraws consent
}

export interface ConsentStore {
  latest(subjectId: string): Promise<ConsentRecord | undefined>;
}

/** Throws B006 unless valid consent covers data collected at `collectedAt`.
 *  `versionInForce` is the ICF version the IRB requires for that date. */
export async function assertConsent(
  store: ConsentStore,
  subjectId: string,
  collectedAt: Date,
  versionInForce: string
): Promise<ConsentRecord> {
  const fail = (msg: string, details: Record<string, unknown>) =>
    new ClinicalTrialError(ClinicalTrialErrorCode.MISSING_CONSENT, msg,
                           { subjectId, ...details });

  const c = await store.latest(subjectId);
  if (!c) throw fail('No signed consent on file', {});
  if (new Date(c.signedAt) > collectedAt)
    throw fail('Data dated before consent was signed',
               { signedAt: c.signedAt, collectedAt: collectedAt.toISOString() });
  if (c.withdrawnAt && new Date(c.withdrawnAt) <= collectedAt)
    throw fail('Consent withdrawn before this collection date',
               { withdrawnAt: c.withdrawnAt });
  if (c.icfVersion !== versionInForce)
    throw fail('Re-consent required', { signed: c.icfVersion, required: versionInForce });
  return c;
}

Two policy points are not code. Withdrawal of consent stops further collection, but data already collected before withdrawal are generally retained and analysed; FDA's guidance on data retention when subjects withdraw says so for FDA-regulated trials, and the protocol and ICF should state it. And a subject may withdraw from treatment while agreeing to continued follow-up, for example of vital status; the consent record should distinguish these so the guard does not block permitted follow-up data.

7.5 Privacy and security

Spec §3.4 requires data encryption and role-based access control and gives no further detail. This section describes the controls that follow from GCP and data protection law without inventing numeric service levels; those belong in each organization's own risk assessment and contracts.

Pseudonymized identifiers

Trial data leave the site keyed by a subject identifier, not by name. The identifier must not encode direct identifiers such as initials, date of birth or hospital record number; STUDY01-001, as in the §7.2 example, is a good pattern. The link between the subject identifier and the person, the subject identification log, stays at the site under the investigator's control. The sponsor's system therefore holds pseudonymized data. Under the GDPR, pseudonymized data are still personal data, because re-identification remains possible with the key; under the HIPAA Privacy Rule, a trial dataset usually does not meet the de-identification standard either, since dates and other elements remain. Korea's Personal Information Protection Act likewise treats pseudonymized information as personal information subject to specific processing rules. The practical result is the same everywhere: the trial database is a personal-data system and needs full access control, encryption and breach procedures.

Role-based access

The matrix below is an illustrative starting point. The row that most often goes wrong is blinding: in a blinded trial, randomization assignments and anything derived from them, including treatment-related safety tabulations, must be visible only to the unblinded roles named in the protocol, typically the independent statistician supporting the DSMB and the people handling drug supply.

RoleEnter / correct dataRaise or close queriesSignSee treatment assignmentRead audit trail
Site coordinatorOwn siteAnswer onlyNoNo (blinded)Own site
Investigator (PI)Own siteAnswer onlyCRFs, AE causalityEmergency unblinding onlyOwn site
Monitor (CRA)NoRaiseNoNoAssigned sites
Data managerNoRaise and closeLockNoAll
Medical coderCoding fields onlyRaiseNoNoCoding changes
Blinded statisticianNoNoAnalysis sign-offNoNo
Unblinded statistician (DSMB support)NoNoNoYesNo
Auditor / inspectorNoNoNoAs requiredAll (read-only)

Encryption and operations

Encryption in transit (TLS for every connection, including between internal services) and at rest (database, backups and exported files) is expected of any system holding health data. Keys should be managed separately from the data, and exports for SDTM, ADaM and submission should be produced in a controlled location rather than on personal machines. Backup and restore must be tested, since a restored database without its audit trail is an incomplete record. Access reviews at defined intervals remove accounts of staff who have left a site; in multi-site trials, stale accounts are one of the most common inspection findings.

7.6 Linking to other WIA standards

The README and spec §10.4 name four related WIA standards. Neither document defines an interface between them and WIA-BIO-010, so the lines below describe intended roles only; an implementation should treat any such link as a separately specified integration.

7.7 Worked integration: one site visit, end to end

The example follows subject STUDY01-001 from the §7.2 JSON to the Week 12 visit (V3) of the diabetes trial registered in §7.1. Baseline was 1 February 2025, so Week 12 falls 84 days later, on 26 April 2025, which is study day 85 in SDTM convention (the baseline date is day 1; there is no day 0). The coordinator enters the vital signs, one laboratory value and two protocol measurements, and makes a decimal slip on the systolic pressure: 14.8 instead of 148.

Step 1 — edit checks

The spec limits and the one cross-field rule used here are implemented as a small function that turns findings into queries. It uses the §8.3 limits, not the normal ranges, and treats 0 as a value.

import { ClinicalTrialErrorCode, DataCollectionRequest } from '@wia/bio-010';

export interface Query { code: ClinicalTrialErrorCode; field: string; text: string }

// Validity limits from spec §8.3 (plausibility), NOT the normal ranges
const SPEC_LIMITS = {
  systolicBP:      { min: 60, max: 250, unit: 'mmHg' },
  diastolicBP:     { min: 40, max: 150, unit: 'mmHg' },
  heartRate:       { min: 30, max: 200, unit: 'bpm' },
  temperature:     { min: 35, max: 42,  unit: '°C' },
  respiratoryRate: { min: 6,  max: 60,  unit: 'breaths/min' },
} as const;

export function checkSpecLimits(req: DataCollectionRequest): Query[] {
  const out: Query[] = [];
  const vs = req.vitalSigns ?? {};
  for (const [field, lim] of Object.entries(SPEC_LIMITS)) {
    const v = vs[field as keyof typeof SPEC_LIMITS];
    if (v === undefined) continue;            // 0 is a value, not "missing"
    if (v < lim.min || v > lim.max) {
      out.push({ code: ClinicalTrialErrorCode.DATA_OUT_OF_RANGE, field,
        text: `${field} = ${v} ${lim.unit} is outside ${lim.min}-${lim.max}; confirm against source` });
    }
  }
  if (vs.systolicBP !== undefined && vs.diastolicBP !== undefined &&
      vs.diastolicBP >= vs.systolicBP) {
    out.push({ code: ClinicalTrialErrorCode.VALIDATION_FAILURE, field: 'diastolicBP',
      text: 'Diastolic BP is not lower than systolic BP; check both values' });
  }
  return out;
}

Step 2 — the capture service

The service runs the consent guard from section 7.4, the SDK's structural check, and the edit checks, then writes the data row, the audit record and any queries in one transaction. If the transaction fails, none of the three exists, which is exactly what an audit trail requires: there is no data row without its creation record.

import { ClinicalTrialSDK, ClinicalTrialError, ClinicalTrialErrorCode,
         DataCollectionRequest } from '@wia/bio-010';
import { assertConsent, ConsentStore } from './consent';
import { checkSpecLimits, Query } from './checks';

export interface Tx { insert(table: string, row: object): Promise<void> }
export interface Db { transaction(fn: (tx: Tx) => Promise<void>): Promise<void> }

interface Ctx { userId: string; role: string; collectedAt: Date; icfVersion: string }

const sdk = new ClinicalTrialSDK();

export async function captureVisit(req: DataCollectionRequest, ctx: Ctx,
                                   deps: { consent: ConsentStore; db: Db }) {
  // 1. Consent first: nothing is written for a subject without valid consent (B006)
  await assertConsent(deps.consent, req.patientId, ctx.collectedAt, ctx.icfVersion);

  // 2. SDK structural check (numeric values) plus its advisory warnings
  const res = sdk.collectPatientData(req);
  if (!res.success || !res.dataId) {
    throw new ClinicalTrialError(ClinicalTrialErrorCode.VALIDATION_FAILURE,
      'Structural validation failed', { errors: res.validationErrors });
  }

  // 3. Spec §8.3 limits and cross-field checks become queries, not rejections
  const queries: Query[] = checkSpecLimits(req);
  const dataId = res.dataId;

  // 4. Data row, audit record and queries commit together or not at all
  await deps.db.transaction(async (tx) => {
    await tx.insert('visit_data', { dataId, ...req,
                                    collectedAt: ctx.collectedAt.toISOString() });
    await tx.insert('audit_trail', { recordId: dataId, action: 'create',
      oldValue: null, newValue: req, userId: ctx.userId, role: ctx.role,
      timestampUtc: new Date().toISOString() });
    for (const q of queries) {
      await tx.insert('queries', { recordId: dataId, ...q, status: 'open' });
    }
  });
  return { dataId, warnings: res.warnings, queries };
}

The call for the Week 12 visit, with the decimal slip:

const out = await captureVisit({
  trialId: 'NCT12345678',
  patientId: 'STUDY01-001',
  visit: 'V3',                                  // Week 12
  vitalSigns: { systolicBP: 14.8, diastolicBP: 86, heartRate: 72, temperature: 36.6 },
  laboratoryResults: { chemistry: { glucose: 142 } },
  measurements: { hba1c: 7.9, weight: 83.9 },
}, { userId: 'u-7731', role: 'site-coordinator',
     collectedAt: new Date('2025-04-26T09:20:00+09:00'), icfVersion: '2.0' }, deps);
console.log(JSON.stringify(out, null, 2));

Expected output (we ran this code against the package source; the dataId is random and will differ):

{
  "dataId": "DATA-1745629500000-k3v9x0a1q",
  "warnings": [
    "Systolic BP (14.8) is out of normal range"
  ],
  "queries": [
    {
      "code": "B007",
      "field": "systolicBP",
      "text": "systolicBP = 14.8 mmHg is outside 60-250; confirm against source"
    },
    {
      "code": "B003",
      "field": "diastolicBP",
      "text": "Diastolic BP is not lower than systolic BP; check both values"
    }
  ]
}

The output shows the gap between the SDK and the spec in one screen. The SDK accepted the record and produced a warning that calls a pressure of 14.8 mmHg merely "out of normal range". The edit checks raised two queries: a B007 range query and a B003 consistency query, because a diastolic pressure of 86 cannot exceed the systolic. The coordinator answers the query by correcting the value to 148 with a reason; that correction produces the audit entry shown in section 7.2, and the data manager closes both queries.

If the same call is made for a subject with no consent on file, it stops at the first line with ClinicalTrialError B006 No signed consent on file, and nothing is written.

Step 3 — the nightly SDTM job

A scheduled job reads the current (corrected) values and produces SDTM rows. It must be deterministic, so that rerunning it produces the same --SEQ numbers for the same records, and it must use a mapping dictionary kept under change control, because the free-form measurements map carries no test codes. In this example the dictionary says that measurements.hba1c maps to LB with LBTESTCD HBA1C, and that glucose is converted from mg/dL to mmol/L by dividing by 18.016 (142 ÷ 18.016 = 7.88). The original result and unit stay in --ORRES/--ORRESU; the standardized ones go to --STRESN/--STRESU.

DOMAINUSUBJID--TESTCD--ORRES--ORRESU--STRESN--STRESUVISITNUM--DTC--DY
VSSTUDY01-001SYSBP148mmHg148mmHg32025-04-2685
VSSTUDY01-001DIABP86mmHg86mmHg32025-04-2685
VSSTUDY01-001HR72beats/min72beats/min32025-04-2685
VSSTUDY01-001TEMP36.6C36.6C32025-04-2685
LBSTUDY01-001GLUC142mg/dL7.88mmol/L32025-04-2685
LBSTUDY01-001HBA1C7.9%7.9%32025-04-2685

Two things are deliberately absent from the SDTM rows: the erroneous 14.8 and the query. SDTM tabulates the final data; the history of how they became final lives in the audit trail, which the inspector reads in the EDC, not in the submission datasets. Weight (83.9 kg) would go to VS with VSTESTCD WEIGHT by the same dictionary. From here, the ADaM step of Chapter 3 derives CHG for HbA1c against the baseline value of 8.5 %, giving −0.6 percentage points at Week 12.

7.8 Chapter checklist and known gaps

Implementation checklist

Known gaps in the v1.0 package relevant to this chapter

With the system around the data in place, the remaining question is how to show that it works. Chapter 8 turns the requirements of v1.0, including the ones discussed here, into test vectors and a self-assessment, and explains what an auditor or inspector looks for when they examine a trial system.