Spaces:
Running
Running
File size: 2,966 Bytes
518343a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | /**
* Tabulated-statistic primitive — the canonical output shape for
* `pipeline.tabulated-statistic.v1` receipts.
*
* The CRISPResso2 lesson re-expressed: every row carries a CI, and
* absence is itself a row (`isNegativeSpace: true`), never a missing
* row. The validators below are what the receipt-write boundary calls.
*/
export interface TabulatedRow {
/** Row label (e.g. allele name, incident class). */
readonly label: string;
/** Integer count, ≥ 0. */
readonly count: number;
/** `count / totalTrials`, in [0, 1]. */
readonly fraction: number;
/** Lower CI bound ∈ [0, 1]; CRISPResso2 lesson: mandatory. */
readonly ciLower: number;
/** Upper CI bound ∈ [0, 1]; CRISPResso2 lesson: mandatory. */
readonly ciUpper: number;
/** True if this row represents "no event" (e.g. unmodified reference). */
readonly isNegativeSpace: boolean;
}
export interface TabulatedStatistic {
/** Total trials underlying the table. */
readonly totalTrials: number;
/** Rows, in the order the pipeline emitted them. */
readonly rows: readonly TabulatedRow[];
/** Method reference (e.g. `'wilson-0.95'`). */
readonly methodRef: string;
/** True iff the schema declares this domain has a meaningful "no event"
* label and therefore requires at least one `isNegativeSpace` row. */
readonly requiresNegativeSpace: boolean;
}
/**
* Validation for the receipt-write boundary.
*
* Rejects rows with `null` / non-finite CI bounds (no claim without an
* interval), rejects bounds that do not surround the point estimate,
* and — when `requiresNegativeSpace` is true — rejects tables that lack
* an explicit negative-space row.
*/
export function validateTabulatedStatistic(stat: TabulatedStatistic): void {
if (!Array.isArray(stat.rows) || stat.rows.length === 0) {
throw new Error('tabulated-statistic: rows[] must be non-empty');
}
let sawNegativeSpace = false;
for (const row of stat.rows) {
if (!Number.isFinite(row.ciLower) || !Number.isFinite(row.ciUpper)) {
throw new Error(`tabulated-statistic: row "${row.label}" missing CI bounds (no claim without an interval)`);
}
if (row.ciLower < 0 || row.ciUpper > 1 || row.ciLower > row.ciUpper) {
throw new Error(`tabulated-statistic: row "${row.label}" CI [${row.ciLower}, ${row.ciUpper}] is malformed`);
}
if (row.fraction < row.ciLower - 1e-9 || row.fraction > row.ciUpper + 1e-9) {
throw new Error(`tabulated-statistic: row "${row.label}" fraction ${row.fraction} not within CI`);
}
if (row.count < 0 || !Number.isInteger(row.count)) {
throw new Error(`tabulated-statistic: row "${row.label}" count ${row.count} must be a non-negative integer`);
}
if (row.isNegativeSpace) sawNegativeSpace = true;
}
if (stat.requiresNegativeSpace && !sawNegativeSpace) {
throw new Error('tabulated-statistic: schema requires a negative-space row but none was emitted (absence is a row)');
}
}
|