Chapter 5
What each function in the WIA-BIO-018 reference SDK and command-line tool actually computes, the thresholds it applies, where it disagrees with the specification, and how to wrap it safely.
Chapter 4 described the records that WIA-BIO-018 wants a system to keep. This chapter turns to the code that produces the numbers and recommendations written into those records: the TypeScript SDK in api/typescript/src/index.ts, its type file types.ts, and the shell tool cli/wia-bio-018.sh. The treatment is deliberately close to the source. For every public method the chapter gives the inputs, the arithmetic, the thresholds and the shape of the result, and it traces at least one call by hand so that a reader can check an implementation against the reference without running it.
Reading the reference implementation this closely has a second purpose. The SDK is version 1.0.0 and it has defects: some are harmless, a few can make a system report a consent as valid when it is not, and one crashes on an input the specification itself uses. None of them is hidden in this chapter. An IRB office that adopts the SDK should know exactly which answers it can rely on, and an engineer should know which ones need a wrapper. Section 5.8 collects the defects in one table. Every numeric result quoted in the chapter was reproduced by running the unmodified reference source (transpiled without type-checking) against the inputs shown.
One caution applies throughout. All thresholds and weights described here, such as the 0.8 comprehension minimum, the 0.30/0.30/0.20/0.15/0.05 weighting or the 1.5 multiplier for vulnerable populations, are design choices of WIA-BIO-018 and its SDK. They are not clinical or legal standards, and no regulation requires them. They make the SDK's behaviour predictable and testable; they do not make its output a verdict.
The SDK exports a single class, BioEthicsSDK. Its constructor takes no arguments and sets no configuration; the only state it holds is a version string returned by getVersion() ("1.0.0"). Every method is synchronous, performs no I/O and does not throw on ordinary input. The same input always produces the same output except for generated identifiers, which embed Date.now() and a random suffix. That makes the SDK easy to unit-test and easy to run inside any service, but it also means the SDK stores nothing: persisting results, attaching them to a consent-form version and writing the audit trail are left entirely to the calling system (Chapter 7).
Four methods are also exported as top-level functions that construct a fresh BioEthicsSDK and delegate to it: validateInformedConsent, assessEthicalRisk, submitIRBProtocol and evaluateGeneEditingEthics. The remaining public methods, performRiskBenefitAnalysis and assessEthicalPrinciple, are reachable only through an instance. The helper getEnhancedProtections, which returns the protection package for a vulnerable population, is declared private; callers cannot invoke it directly and see its output only as entries in requiredProtections.
| Method | Input type | Output type | Key rule |
|---|---|---|---|
validateInformedConsent | ConsentValidation | ConsentValidationResult | Valid only if there are no violations and the weighted compliance score is at least 0.8 |
assessEthicalRisk | EthicalRiskAssessment | EthicalRiskResult | Benefit-to-risk ratio from list counts; approve / revise / reject |
submitIRBProtocol | IRBProtocol | IRBSubmission | Routes to exempt, expedited or full-board; status always submitted |
evaluateGeneEditingEthics | GeneEditingEvaluation | GeneEditingDecision | Decision tree over type × purpose, then evidence and consent checks |
performRiskBenefitAnalysis | studyId, Risk[], Benefit[], justiceFactor | RiskBenefitAnalysis | Σbenefit ÷ Σrisk × justice factor, thresholds 2.0 / 1.5 / 1.0 / 0.7 |
assessEthicalPrinciple | EthicalPrinciple, evidence, concerns | PrincipleAssessment | 0.25 per evidence item, −0.2 per concern, clamped to 0–1 |
getVersion | none | string | Returns "1.0.0" |
submitIRBProtocol does not submit anything to anyone. It chooses a review level and wraps the protocol in an IRBSubmission whose status is always submitted and whose submissionId has the form IRB-<milliseconds>-<9 random base-36 characters>. The routing rule is short: a protocol at minimal risk with no entries in participants.vulnerablePopulations goes to exempt if its free-text studyType contains one of four phrases (educational research, anonymous surveys, observation of public behavior, existing de-identified data) and to expedited otherwise; a minor-increase protocol without vulnerable groups goes to expedited; everything else goes to full-board. Chapter 6 discusses how this compares with real regulatory categories. For this chapter it is enough to note that the exempt test is a case-insensitive substring match on free text, so it is only a first suggestion for the IRB office to confirm.
assessEthicalPrinciple scores one of the four principles from two string lists: each evidence item adds 0.25 (capped at 1.0 after four items) and each concern subtracts 0.2, with the result clamped to the range 0–1. The function never reads the strings. Four one-word evidence entries score exactly as well as four pages of documented safeguards. It is a counter, not an assessment, and the result should be stored only next to the evidence it counted.
The package.json names the package @wia/bio-018 and the README shows npm install @wia/bio-018, but this book does not assume that a published package exists; treat the SDK as source that you build yourself. Before you do, be aware that the source as shipped does not pass tsc --noEmit (the typecheck script in package.json). Both files end with an export block that re-exports names already exported by their declarations, which TypeScript reports as conflicting exports, and evaluateGeneEditingEthics returns a decision value ('revise') that its own return type does not allow. The first problem is fixed by deleting the redundant trailing export blocks; the second is a real logic issue discussed in section 5.5.
validateInformedConsent
The method takes a ConsentValidation: participant and study identifiers, a consentForm object of booleans (declared as the ten-field ConsentFormElements), an optional participantCapacity, an optional comprehensionScore between 0 and 1, a witnessPresent flag and a signature object with booleans for participant, witness and lar. Two defaults matter a great deal. If participantCapacity is omitted the SDK assumes 'full', and if comprehensionScore is omitted it assumes 1.0, a perfect score. A caller that forgets to pass the comprehension result therefore gets the most favourable possible answer. A conformant wrapper should make both fields mandatory, or pass 0 when no assessment was recorded, as the example in section 5.6 does.
The method collects four lists: violations, warnings, recommendations and required actions. It then applies the following checks.
consentForm whose value is true and divides by the number of entries. Anything below 1.0 is a violation that names the missing keys.diminished or severely-impaired participants, a missing LAR signature is a violation; a present one produces an informational warning. A borderline participant produces a warning recommending support and periodic reassessment.full or comprehension is below 0.85, that is a violation.full or comprehension is below 0.9. Note that this threshold (0.9) differs from the one used in step 4 (0.85).| Component | Weight | How the component value is obtained |
|---|---|---|
| Form elements | 0.30 | Share of consentForm entries that are true |
| Comprehension | 0.30 | The comprehensionScore as given (default 1.0) |
| Capacity | 0.20 | full 1.0 · borderline 0.85 · diminished 0.65 · severely-impaired 0.40 |
| Signature | 0.15 | 1.0 if the participant or the LAR signed, otherwise 0 |
| Witness | 0.05 | 1.0 if a witness was present and signed, or if no witness was needed (full capacity and comprehension ≥ 0.85); otherwise 0.5 |
The result is isValid = (violations.length === 0) && (complianceScore >= 0.8). The score also selects one closing recommendation: at least 0.95 is "excellent", at least 0.8 "good", at least 0.7 "needs improvement", and anything lower "inadequate — do not proceed". Because the score is a weighted average, it can be high while a violation still makes the consent invalid, and the two must never be read separately.
The following traces use inputs derived from the consent record in spec §8.3.1 (comprehension 0.92 by teach-back, witness present and signed). They show how the same arithmetic behaves as single inputs change.
| Case | Capacity / comprehension / signatures | Score arithmetic | Result |
|---|---|---|---|
| A. Baseline | full / 0.92 / participant + witness | 0.30 + 0.276 + 0.20 + 0.15 + 0.05 = 0.976 | valid, "excellent" |
| B. Just below the line | full / 0.79 / participant + witness | 0.30 + 0.237 + 0.20 + 0.15 + 0.05 = 0.937 | invalid: comprehension violation, despite a "good" score |
| C. LAR route | diminished / 0.85 / LAR + witness, no participant signature | 0.30 + 0.255 + 0.13 + 0.15 + 0.05 = 0.885 | valid, with a comprehension warning and an LAR note |
Case A reproduces the complianceScore of 0.98 printed in the spec's example record once 0.976 is rounded to two decimals (Chapter 4 checks the arithmetic); a system should store the unrounded value it actually computed and round only for display. Case B is the important one. A participant who answered 79% of comprehension items correctly produces a score of 0.937, comfortably above the 0.8 line, yet the consent is invalid because the comprehension check is a hard rule. Case C shows the LAR route working as intended: the participant does not sign, the representative does, the capacity weight drops the score to 0.885, and the consent remains valid.
Completeness is computed from whatever keys the caller passes. The README example sends the six-key form of spec §8.2.1 with every key true and no comprehension score; the SDK returns a perfect score of 1.0 and isValid: true, although four of the ten required disclosures, including future use of data and samples, were never checked. An empty consentForm is worse: zero divided by zero yields NaN, no violation is recorded, and the only reason the result is invalid is that NaN >= 0.8 is false. A wrapper should always build the full ten-field ConsentFormElements object itself and reject any input that lacks a key.
assessEthicalRisk
assessEthicalRisk gives a quick screening view of a study from six fields: a free-text studyType, a targetPopulation (either 'general-adults' or one of the nine VulnerablePopulation values), an interventionType, lists of potentialBenefits and potentialRisks, and a riskLevel. Two lookup tables translate the categorical inputs into numbers.
| Intervention type | Intervention risk | Risk level | Base risk |
|---|---|---|---|
observational | 0.1 | minimal | 0.2 |
behavioral | 0.2 | minor-increase | 0.4 |
device | 0.5 | greater-than-minimal | 0.7 |
drug | 0.6 | high | 0.9 |
surgical | 0.8 | (unknown value) | 0.5 |
gene-therapy | 0.9 | ||
other or unknown value | 0.5 |
multiplier = (targetPopulation === 'general-adults') ? 1.0 : 1.5
totalRisk = (baseRisk + interventionRisk) / 2 * multiplier + 0.05 * potentialRisks.length
benefit = min(1.0, 0.2 * potentialBenefits.length)
ratio = benefit / max(totalRisk, 0.1) // a benefit-to-risk ratio
ethicalScore = min(1.0, ratio * 0.5)
Despite the field name riskBenefitRatio, the quantity is benefit divided by risk, so larger is better. The recommendation is then chosen by the first matching rule: approve if ethicalScore ≥ 0.7 and ratio ≥ 1.5; approve with a monitoring warning if ethicalScore ≥ 0.5 and ratio ≥ 1.0; revise if ethicalScore ≥ 0.3 or ratio ≥ 0.7; otherwise reject. Because ethicalScore is simply half the ratio (capped at 1), the double conditions collapse into single cut-offs on the ratio: approve at 1.5 or more, approve with warning from 1.0, revise from 0.6, reject below 0.6. Knowing this makes test design much simpler (Chapter 8).
Protections are then appended. The revise branch adds enhanced safety monitoring and a Data Safety Monitoring Board (DSMB). A greater-than-minimal or high risk level adds full IRB review, independent data monitoring and regular safety reviews. A gene-therapy intervention adds long-term follow-up "minimum 5 years", genetic counseling and a family-notification protocol. Any vulnerable population, or any risk level other than minimal, adds IRB approval with continuing review. Duplicates are removed at the end. The five-year figure is an SDK default and should not be read as sufficient: the US FDA's guidance on long-term follow-up after gene-therapy products recommends observation of up to 15 years for products such as integrating vectors and genome-editing products, and a program should follow the rule of its regulator.
Consider a hypothetical first-in-human study of a gene therapy in adult volunteers, recorded as interventionType: 'gene-therapy', riskLevel: 'greater-than-minimal', targetPopulation: 'general-adults', with three listed benefits and four listed risks.
| Step | Computation | Value |
|---|---|---|
| Base risk and intervention risk | lookup | 0.7 and 0.9 |
| Population multiplier | general-adults | 1.0 |
| Averaged risk | (0.7 + 0.9) / 2 × 1.0 | 0.8 |
| List penalty | 4 × 0.05 | 0.2 |
| Total risk | 0.8 + 0.2 | 1.0 |
| Benefit | min(1, 3 × 0.2) | 0.6 |
| Ratio | 0.6 / 1.0 | 0.6 |
| Ethical score | 0.6 × 0.5 | 0.3 |
| Rule matched | not ≥ 0.7; not ≥ 0.5; ethicalScore ≥ 0.3 | revise |
The call returns nine protections: the DSMB and enhanced monitoring from the revise branch, three from the risk level, three from gene therapy and the continuing-review item. Two lessons follow. First, the study sits exactly on the revise/reject boundary. In floating-point arithmetic, 3 × 0.2 is 0.6000000000000001 and the ethical score becomes 0.30000000000000004, which is why the comparison with 0.3 succeeds. An implementation in another language that computes slightly differently, or rounds earlier, could return reject for the same study. Round scores to a fixed number of decimals before comparing, and write boundary tests. Second, listing a fifth risk, even a trivial one, would raise the total risk to 1.05, drop the ethical score below 0.3 and turn the result into reject. The score measures how many lines were written, not how serious they are.
The same method also shows the effect of the vulnerability multiplier. A drug study in children at minor-increase risk with two benefits and two risks gives (0.4 + 0.6) / 2 × 1.5 + 0.1 = 0.85 total risk, a benefit of 0.4, a ratio of about 0.47 and an ethical score of about 0.24, so the recommendation is reject, together with the child-specific protection list. The 1.5 multiplier is a blunt instrument: it treats every vulnerable group identically and applies whether or not the protections for that group are already in the protocol.
performRiskBenefitAnalysis
Where assessEthicalRisk counts list entries, performRiskBenefitAnalysis works with structured Risk and Benefit records (Chapter 4). It sums the score fields of each list, divides total benefit by the larger of total risk and 0.1, and multiplies the result by a caller-supplied justiceFactor between 0 and 1 (default 1.0). This is the ratio form of the principle calculation introduced in Chapter 1; the README's subtractive formula, (benefits − risks) × justice factor, does not match the code and should not be used.
The method never recomputes score. The types document it as probability × severity for a risk and probability × magnitude for a benefit, but the SDK trusts whatever number the caller put there. A wrapper should compute the score itself, or at least assert that the stored value matches the product, before calling the analysis.
| Adjusted ratio | Justice factor | Recommendation |
|---|---|---|
| ≥ 2.0 | ≥ 0.9 | approve ("excellent profile, equitable distribution") |
| ≥ 1.5 | ≥ 0.85 | approve ("favorable profile") |
| ≥ 1.0 | ≥ 0.8 | approve-with-modifications |
| ≥ 0.7 or | ≥ 0.7 | defer |
| otherwise | reject | |
Separately, isEthicallyAcceptable is true when the adjusted ratio is at least 1.0 and the justice factor at least 0.8. As an illustrative example, take one physical risk (probability 0.1, severity 6, score 0.6), one privacy risk (0.2 × 3 = 0.6) and one direct benefit (probability 0.4, magnitude 8, score 3.2), with a justice factor of 0.85. The raw ratio is 3.2 / 1.2 ≈ 2.67; adjusted, it is ≈ 2.27. That misses the first rule because the justice factor is below 0.9, matches the second, and the result is approve with isEthicallyAcceptable: true.
Two details catch people out. The returned field riskBenefitRatio holds the adjusted ratio, not the raw one, so a reviewer who wants to see how much the justice factor changed the outcome must recompute it from the totals, which the result also contains. And the defer rule uses or: a study with an adjusted ratio of 0.1 but a justice factor of 0.75 is deferred rather than rejected, because the justice test alone is enough to reach that branch. If an institution wants weak studies rejected regardless of distribution, it must add that rule in its own layer.
evaluateGeneEditingEthics decision tree
The gene-editing evaluator encodes the somatic/germline × therapeutic/enhancement/research matrix discussed in Chapter 3. It takes a GeneEditingEvaluation containing the editing type and purpose, three scientific-validity booleans, four risk estimates between 0 and 1, and four ethical booleans. Its output is a GeneEditingDecision with a decision, a rationale and lists of conditions, oversight bodies and monitoring requirements.
germline
├─ enhancement → reject ("prohibited in most jurisdictions")
├─ therapeutic → defer (moratorium; 4 conditions, 4 oversight bodies,
│ multi-generational monitoring)
└─ research → falls through to the default below ← defect
somatic
├─ enhancement → reject
├─ no preclinical evidence → reject ("complete preclinical studies")
├─ mechanism not understood → add condition, continue
├─ safety not demonstrated → defer (IRB + DSMB)
├─ consent not obtained → reject
├─ benefits do not outweigh risks → 'revise' ← not a legal decision value
├─ avgRisk = mean of 4 risks; if avgRisk > 0.5 add mitigation condition
│ and weekly monitoring for the first month
├─ therapeutic and avgRisk ≤ 0.5 → approve
├─ research → approve-research-only
└─ otherwise → defer
default → defer ("insufficient information", ethics committee review)
On the approval paths the evaluator adds standard oversight (IRB approval with continuing review and an institutional biosafety committee, plus "FDA IND approval (or equivalent)" for therapeutic use) and standard monitoring (long-term follow-up "minimum 5 years", off-target surveillance, immune-response monitoring, efficacy assessment). If there are acceptable alternatives (noAlternatives false) it adds a condition to justify the choice, and if equitable access is not planned it adds a condition to develop such a plan. These are conditions on an approval, not grounds for refusal.
As an illustrative example, take a somatic therapeutic edit with preclinical evidence, understood mechanism, demonstrated safety, consent obtained and a favourable balance, risk estimates of 0.3, 0.2, 0.1 and 0.6 (mean 0.3), and no equity plan. The result is approve with one condition (develop an equitable access plan), three oversight entries and four monitoring entries. The same input marked purpose: 'research' returns approve-research-only with the added condition "No clinical application without separate approval".
Several properties of the tree need to be understood before its output is shown to a committee.
defer with "insufficient information" and none of those conditions. The 14-day limit itself is a real, widely adopted rule, but the ISSCR's 2021 guidelines no longer treat it as an absolute bar, so any system that restores the conditions should cite the governing national rule rather than hard-code it.decision: 'revise', which is not one of the four values declared in GeneEditingDecision (approve, approve-research-only, defer, reject). TypeScript flags it at compile time; JavaScript callers receive it at run time, and a switch over the four declared values will silently skip it. Map it to defer with the original rationale preserved.regulatoryCompliance and internationalConsensus are part of the input type, but the method never reads them. A germline therapeutic proposal is deferred whether or not a consensus exists, and a somatic proposal can be approved even if regulatoryCompliance is false. A wrapper should at least refuse approval when regulatoryCompliance is false.Result pattern
types.ts defines the enum BioEthicsErrorCode with ten codes and a class BioEthicsError that carries a code, a message and optional details. Spec §8.4 lists only the first six codes, with an action for each; E007 to E010 exist only in the type file. None of the SDK's methods creates a BioEthicsError: validateInformedConsent reports problems as English sentences in violations, and the other methods express problems through their recommendation or decision. The error codes are therefore a vocabulary that the integrating system must apply itself.
| Code | Enum name | Action | Typical source in the SDK |
|---|---|---|---|
| E001 | INCOMPLETE_CONSENT | Complete all required elements (§8.4) | Form-completeness, signature and witness violations |
| E002 | INSUFFICIENT_COMPREHENSION | Re-educate and reassess (§8.4) | Comprehension below 0.8 |
| E003 | COERCION_DETECTED | Remove pressure, allow time (§8.4) | None: voluntariness is not an input to the validator |
| E004 | CAPACITY_CONCERNS | Obtain LAR consent (§8.4) | Diminished or severely impaired capacity without LAR |
| E005 | IRB_APPROVAL_REQUIRED | Submit protocol for review (§8.4) | Caller's own check before enrolment |
| E006 | PRIVACY_VIOLATION | Enhance data protection (§8.4) | None: raised by the privacy component (Chapter 7) |
| E007 | UNACCEPTABLE_RISK | Book's recommendation: stop and revise the design; return to the IRB | reject from assessEthicalRisk or performRiskBenefitAnalysis |
| E008 | VULNERABLE_POPULATION | Book's recommendation: add the population's protection package and route to full-board review | A vulnerable population without its protections in the protocol |
| E009 | INVALID_GENE_EDITING | Book's recommendation: block clinical use; record the rationale | reject from evaluateGeneEditingEthics |
| E010 | REGULATORY_NONCOMPLIANCE | Book's recommendation: halt until the gap in the applicable regulation is closed | regulatoryCompliance: false (not read by the SDK) |
The type file also defines Result<T, E>, a discriminated union of { success: true; data: T } and { success: false; error: E }, and its promise form AsyncResult. No SDK method uses them, but they are a sensible shape for a wrapper: the caller cannot read data without first checking success, which forces the invalid path to be handled.
The following wrapper takes a record in the shape of spec §8.3.1, converts its eleven-key elements object into the ten-field ConsentFormElements recommended in Chapter 4, calls the validator and returns a Result with coded errors. It does not rely on the SDK's defaults: a missing comprehension assessment is passed as 0.
import {
validateInformedConsent, ConsentFormElements, ConsentValidationResult,
BioEthicsErrorCode, BioEthicsError, Result,
} from '@wia/bio-018'; // or a relative path to your own build
interface SpecConsentRecord { // the parts of §8.3.1 used here
participantId: string;
studyId: string;
consentDate: string; // ISO-8601 UTC
elements: Record<string, boolean>;
participantCapacity?: 'full' | 'borderline' | 'diminished' | 'severely-impaired';
comprehension?: { score: number; method: string; assessor: string };
signatures: {
participant: { signed: boolean };
witness?: { signed: boolean };
legallyAuthorizedRepresentative?: { signed: boolean };
};
}
// 11 spec keys → 10 canonical fields. There is no spec key for future use.
function toFormElements(e: Record<string, boolean>): ConsentFormElements {
return {
purposeDisclosed: e.purpose === true && e.procedures === true,
durationExplained: e.duration === true,
risksDisclosed: e.risks === true,
benefitsExplained: e.benefits === true,
alternativesProvided: e.alternatives === true,
withdrawalRights: e.voluntary === true && e.withdrawal === true,
privacyProtection: e.confidentiality === true,
compensationExplained: e.compensation === true,
contactProvided: e.contact === true,
futureUseDisclosed: e.futureUse === true,
};
}
function toCode(violation: string): BioEthicsErrorCode {
if (violation.startsWith('Insufficient comprehension'))
return BioEthicsErrorCode.INSUFFICIENT_COMPREHENSION; // E002
if (violation.includes('Legally Authorized Representative'))
return BioEthicsErrorCode.CAPACITY_CONCERNS; // E004
return BioEthicsErrorCode.INCOMPLETE_CONSENT; // E001
}
export function checkConsent(
r: SpecConsentRecord,
): Result<ConsentValidationResult, BioEthicsError[]> {
const witness = r.signatures.witness;
const result = validateInformedConsent({
participantId: r.participantId,
studyId: r.studyId,
consentForm: toFormElements(r.elements),
participantCapacity: r.participantCapacity ?? 'full',
comprehensionScore: r.comprehension?.score ?? 0, // never default to 1.0
witnessPresent: witness !== undefined,
signature: {
participant: r.signatures.participant.signed,
witness: witness?.signed ?? false,
lar: r.signatures.legallyAuthorizedRepresentative?.signed ?? false,
date: new Date(r.consentDate),
},
});
if (result.isValid) return { success: true, data: result };
const errors = result.violations.map((v) => new BioEthicsError(toCode(v), v));
if (errors.length === 0) { // e.g. NaN or low score with no violation text
errors.push(new BioEthicsError(BioEthicsErrorCode.INCOMPLETE_CONSENT,
`complianceScore ${result.complianceScore} is below 0.8`));
}
return { success: false, error: errors };
}
Run against the §8.3.1 example exactly as printed, this wrapper returns success: false with a single E001 error, "Incomplete consent form. Missing: futureUseDisclosed". The failure is correct, not a bug in the wrapper: the spec's example record has no element for future use of data and samples, which is one of the ten disclosures of spec §3.1.1 and is especially important for biobanks (Chapter 2). Add a futureUse: true element, representing a form that actually discloses it, and the same record validates with a compliance score of 0.976. This is exactly the discrepancy that the three inconsistent consent shapes of Chapter 4 create, surfaced as a coded error rather than a silent pass.
Two design notes on the example. Matching English sentences to codes is brittle: if the SDK's wording changes, the mapping falls back to E001. It is acceptable for a thin wrapper around a pinned SDK version, but a longer-lived system should move the checks into its own code, or patch the SDK to return codes directly, and keep a regression test for each message. Second, the wrapper deliberately treats "witness present but signature missing" as E001 rather than inventing a code, because the standard defines none for it.
cli/wia-bio-018.sh is a Bash script with four commands plus help and version. It needs bc for its floating-point comparisons. It is useful for demonstrations and training because it explains each step in coloured text, but it does not call the SDK: every rule is re-implemented in shell, and several are simplified or replaced with constants. Its output should not be recorded as the result of an ethics check.
| Command | Options (default) | What it really computes |
|---|---|---|
validate-consent | --participant (P-12345), --study (STUDY-001), --comprehension (0.85), --capacity (full), --witness (yes) | Prints comprehension, capacity and witness checks, then a fixed compliance score of 0.9 and "Consent is VALID" regardless of the checks above |
assess-risk | --study-type (gene-therapy), --population (adults), --intervention (drug), --risk-level (minimal) | Ratio = fixed benefit 0.7 ÷ intervention risk; ignores risk level and population in the ratio; uses the SDK's thresholds 1.5 / 1.0 / 0.7 |
submit-irb | --protocol (protocol.json), --institution (a placeholder value), --risk-level (minimal), --vulnerable (no) | Never reads the protocol file; never routes to exempt; prints hard-coded review timelines that do not come from the spec |
evaluate-gene-editing | --type (somatic), --purpose (therapeutic), --disease (sickle-cell) | Type × purpose only; somatic therapeutic is always "APPROVE", with no evidence, safety or consent checks |
version, help | none | Version 1.0.0 and usage text |
$ wia-bio-018 validate-consent --comprehension 0.5 --capacity diminished --witness no
✗ Comprehension: FAIL (0.5 < 0.8)
⚠ Capacity: DIMINISHED - Needs LAR, can assent
✗ Witness: Required but not present
Overall Compliance Score: 0.9
✓ Compliance: GOOD (0.9)
✓ Consent is VALID
The excerpt above (colour codes removed) shows the most serious CLI defect: two failed checks followed by "Consent is VALID". The same inputs passed to the SDK give an invalid result with three violations. Other differences are smaller but real. The README says to run wia-bio-018 --version, but the script recognises only the version command and exits with "Unknown command" for the flag. The help text lists only three capacity levels and three risk levels, although the script accepts the fourth capacity value. The population default adults matches the spec's §8.2.2 type but not the SDK, which expects general-adults; passing general-adults to the CLI falls into its generic "requires ethical review" branch. Gene-editing results also differ: the CLI defers all germline research with a "research only" note, while the SDK returns the generic default.
A practical rule follows: use the CLI to teach the vocabulary and the shape of the checks, and use the SDK, wrapped as in section 5.6, for anything that is stored. If a command-line interface is needed in production, build a thin one on top of the wrapper so that the terminal and the service can never disagree.
The following table lists the defects found while tracing the reference implementation for this chapter. Each one was reproduced with the unmodified source. The workaround column assumes a wrapper layer like the one in section 5.6; Chapter 8 turns each row into a regression test.
| Defect | Symptom | Workaround |
|---|---|---|
| Consent completeness counts the keys passed | Six-key form passes at 1.0; an empty form yields NaN with no violation | Always build all ten ConsentFormElements fields; reject missing keys |
| Optimistic defaults | Omitted comprehension counts as 1.0; omitted capacity as full | Make both mandatory in the wrapper; pass 0 when no assessment exists |
| Risk and benefit scored by list length | Adding a trivial risk line can turn revise into reject; content is never read | Show the scores as screening aids only; base decisions on performRiskBenefitAnalysis with real probabilities and severities, reviewed by people |
'adults' vs 'general-adults' | The spec §8.2.2 and CLI value 'adults' is treated as vulnerable; the private protection lookup returns undefined and the call throws a TypeError | Normalise 'adults' to 'general-adults' before the call; reject unknown populations |
| Germline research falls through | Returns generic defer, and the three prepared conditions are discarded | Handle germline research in the wrapper and attach the conditions required by the governing rule |
Somatic 'revise' | Decision outside the declared union; compile error; skipped by exhaustive switches | Map to defer, keeping the rationale |
| Ignored gene-editing fields | regulatoryCompliance and internationalConsensus are never read | Refuse approval (E010) when regulatoryCompliance is false |
| OR in fall-back branches | Justice factor ≥ 0.7 alone yields defer instead of reject; revise in assessEthicalRisk is reached at ratio 0.6 | Add explicit institutional cut-offs above the SDK |
| Floating-point boundaries | Results at 0.3, 0.8 or 1.5 depend on binary rounding | Round to a fixed precision before comparing; test both sides of each boundary |
| Adjusted value stored under raw name | riskBenefitRatio in RiskBenefitAnalysis already includes the justice factor | Store both raw and adjusted ratios in the audit record |
| Source does not type-check | Duplicate export blocks; the 'revise' value | Remove the trailing re-export blocks; fix the decision value |
| README examples use invalid values | participantCapacity: 'competent' (becomes undefined capacity weight, so the score is NaN); interventionType: 'somatic-gene-editing' (falls back to 0.5); riskLevel omitted (falls back to 0.5); targetPopulation: 'adults' (throws) | Do not copy README snippets; use the type definitions as the reference |
| CLI diverges from SDK | Fixed compliance score 0.9; fixed benefit 0.7; no exempt route; --version unsupported | Use the CLI for demonstration only |
None of these defects changes the purpose of the SDK. Its value lies in making the standard's rules explicit and repeatable, so that two systems given the same consent record reach the same conclusion and can explain it. That value survives the defects as long as the integrating system knows about them, guards against them at its boundary, and keeps the human decision separate from the computed one. The thresholds and weights are the standard's proposals; the IRB remains the body that decides.
Key points of this chapter
validateInformedConsent requires both zero violations and a weighted score of at least 0.8. A 79% comprehension result is invalid even with a 0.937 score.assessEthicalRisk reduces to cut-offs on a benefit-to-risk ratio computed from list lengths (approve at 1.5 or more, approve with warning from 1.0, revise from 0.6); it is a screening aid only.performRiskBenefitAnalysis uses the ratio form with a justice factor and returns the adjusted ratio; it trusts caller-supplied scores.'revise'.comprehensionScore receive a more favourable result than one that passes 0.8? How should a wrapper prevent this?assessEthicalRisk reduce to cut-offs of 1.5, 1.0 and 0.6 on the ratio.minimal risk with two benefits and one risk, trace assessEthicalRisk to its recommendation.evaluateGeneEditingEthics. What does each return, and why is the first result a defect?validate-consent command can disagree with the SDK for the same participant.