@tangle-network/agent-eval 0.135.1 → 0.135.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/dist/analyst/index.js +1 -1
- package/dist/{analyst-LsnNpSkm.js → analyst-j5je5J7c.js} +2 -2
- package/dist/{analyst-LsnNpSkm.js.map → analyst-j5je5J7c.js.map} +1 -1
- package/dist/{analyze-runs-DMo3Lb_y.d.ts → analyze-runs-Cda5Xkj1.d.ts} +3 -3
- package/dist/{analyze-runs-DMo3Lb_y.d.ts.map → analyze-runs-Cda5Xkj1.d.ts.map} +1 -1
- package/dist/{analyze-runs-qk8op0tN.js → analyze-runs-jjCmF8pU.js} +14 -7
- package/dist/analyze-runs-jjCmF8pU.js.map +1 -0
- package/dist/{baseline-BaPxoROc.js → baseline-BUeFcgrn.js} +2 -2
- package/dist/{baseline-BaPxoROc.js.map → baseline-BUeFcgrn.js.map} +1 -1
- package/dist/benchmarks/index.d.ts +1 -1
- package/dist/benchmarks/index.js +1 -1
- package/dist/{benchmarks-DwFrCrCS.js → benchmarks-Dfgm9ts5.js} +3 -3
- package/dist/{benchmarks-DwFrCrCS.js.map → benchmarks-Dfgm9ts5.js.map} +1 -1
- package/dist/builder-eval/index.js +1 -1
- package/dist/campaign/index.d.ts +2 -2
- package/dist/campaign/index.js +2 -2
- package/dist/{campaign-B0oyngTs.js → campaign-Dz8uQnhC.js} +6 -6
- package/dist/{campaign-B0oyngTs.js.map → campaign-Dz8uQnhC.js.map} +1 -1
- package/dist/{client-BIyh1RCr.d.ts → client-DcvgkaZi.d.ts} +13 -4
- package/dist/client-DcvgkaZi.d.ts.map +1 -0
- package/dist/contract/index.d.ts +3 -3
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/contract/index.js +20 -8
- package/dist/contract/index.js.map +1 -1
- package/dist/{default-registry-BAhV-lbE.js → default-registry-CHmdy2An.js} +2 -2
- package/dist/{default-registry-BAhV-lbE.js.map → default-registry-CHmdy2An.js.map} +1 -1
- package/dist/{eval-campaign-aMG73hvX.js → eval-campaign-Cc8WZJ6b.js} +2 -2
- package/dist/{eval-campaign-aMG73hvX.js.map → eval-campaign-Cc8WZJ6b.js.map} +1 -1
- package/dist/{extract-usage-2j25whHw.js → extract-usage-DIQpN-ww.js} +2 -2
- package/dist/{extract-usage-2j25whHw.js.map → extract-usage-DIQpN-ww.js.map} +1 -1
- package/dist/hosted/index.d.ts +1 -1
- package/dist/{index-BoJNQR6n.d.ts → index-B4Fjfo5U.d.ts} +93 -15
- package/dist/index-B4Fjfo5U.d.ts.map +1 -0
- package/dist/{index-C21xKtxu.d.ts → index-CQsJcqch.d.ts} +3 -3
- package/dist/{index-C21xKtxu.d.ts.map → index-CQsJcqch.d.ts.map} +1 -1
- package/dist/{index-DSC51roc2.d.ts → index-DSC51roc.d.ts} +1 -1
- package/dist/index-DSC51roc.d.ts.map +1 -0
- package/dist/index.d.ts +42 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +136 -58
- package/dist/index.js.map +1 -1
- package/dist/matrix/index.d.ts +1 -1
- package/dist/meta-eval/index.d.ts +1 -2
- package/dist/meta-eval/index.d.ts.map +1 -1
- package/dist/meta-eval/index.js +2 -2
- package/dist/multishot/index.d.ts +1 -1
- package/dist/openapi.json +1 -1
- package/dist/{paired-arms-CA_8pN01.js → paired-arms-BbFKrAU-.js} +2 -2
- package/dist/{paired-arms-CA_8pN01.js.map → paired-arms-BbFKrAU-.js.map} +1 -1
- package/dist/pipelines/index.js +2 -2
- package/dist/{release-report-BVZBmRZp.js → release-report-DooPguBc.js} +4 -3
- package/dist/{release-report-BVZBmRZp.js.map → release-report-DooPguBc.js.map} +1 -1
- package/dist/{release-report-CuULWKyk.d.ts → release-report-DpBxGGI1.d.ts} +2 -2
- package/dist/{release-report-CuULWKyk.d.ts.map → release-report-DpBxGGI1.d.ts.map} +1 -1
- package/dist/{replay-BI6CVKkp.d.ts → replay-BRfMIs81.d.ts} +39 -21
- package/dist/replay-BRfMIs81.d.ts.map +1 -0
- package/dist/{replay-CJfGLdx4.js → replay-C6wRg47C.js} +23 -41
- package/dist/replay-C6wRg47C.js.map +1 -0
- package/dist/reporting.d.ts +3 -3
- package/dist/reporting.js +4 -4
- package/dist/{researcher-DVtruQ9U.d.ts → researcher-Doo95b50.d.ts} +2 -2
- package/dist/{researcher-DVtruQ9U.d.ts.map → researcher-Doo95b50.d.ts.map} +1 -1
- package/dist/{reward-hacking-DCdRK9TY.js → reward-hacking-a-kYs0-i.js} +2 -2
- package/dist/{reward-hacking-DCdRK9TY.js.map → reward-hacking-a-kYs0-i.js.map} +1 -1
- package/dist/rl.d.ts +45 -3
- package/dist/rl.d.ts.map +1 -1
- package/dist/rl.js +108 -22
- package/dist/rl.js.map +1 -1
- package/dist/{rubric-predictive-validity-D6Q6n9oq.js → rubric-predictive-validity-BJf-8ejY.js} +2 -2
- package/dist/{rubric-predictive-validity-D6Q6n9oq.js.map → rubric-predictive-validity-BJf-8ejY.js.map} +1 -1
- package/dist/{skillopt-optimization-method-C9yGg-4C.js → skillopt-optimization-method-0UmPD6aP.js} +340 -54
- package/dist/skillopt-optimization-method-0UmPD6aP.js.map +1 -0
- package/dist/{skillopt-optimization-method-DJ3l4w8W.d.ts → skillopt-optimization-method-CwSYkv35.d.ts} +39 -9
- package/dist/skillopt-optimization-method-CwSYkv35.d.ts.map +1 -0
- package/dist/{statistics-D_4Snl-5.d.ts → statistics-CKOqre5S.d.ts} +329 -3
- package/dist/statistics-CKOqre5S.d.ts.map +1 -0
- package/dist/{statistics-RwRNu2__.js → statistics-CnGCLLqc.js} +315 -2
- package/dist/statistics-CnGCLLqc.js.map +1 -0
- package/dist/{summary-report-BxtossFi.js → summary-report-BEk8OFLs.js} +11 -6
- package/dist/summary-report-BEk8OFLs.js.map +1 -0
- package/dist/{summary-report-DGp0-_XO.d.ts → summary-report-CPMINBqs.d.ts} +182 -7
- package/dist/summary-report-CPMINBqs.d.ts.map +1 -0
- package/dist/{tools-BmuN627J.js → tools-D8yTtNSN.js} +132 -27
- package/dist/tools-D8yTtNSN.js.map +1 -0
- package/dist/traces.d.ts +2 -2
- package/dist/traces.js +5 -5
- package/docs/trace-analysis.md +24 -0
- package/package.json +1 -1
- package/dist/analyze-runs-qk8op0tN.js.map +0 -1
- package/dist/client-BIyh1RCr.d.ts.map +0 -1
- package/dist/index-BoJNQR6n.d.ts.map +0 -1
- package/dist/index-DSC51roc2.d.ts.map +0 -1
- package/dist/judge-calibration-DFtEMlde.d.ts +0 -146
- package/dist/judge-calibration-DFtEMlde.d.ts.map +0 -1
- package/dist/replay-BI6CVKkp.d.ts.map +0 -1
- package/dist/replay-CJfGLdx4.js.map +0 -1
- package/dist/skillopt-optimization-method-C9yGg-4C.js.map +0 -1
- package/dist/skillopt-optimization-method-DJ3l4w8W.d.ts.map +0 -1
- package/dist/statistics-D_4Snl-5.d.ts.map +0 -1
- package/dist/statistics-RwRNu2__.js.map +0 -1
- package/dist/summary-report-BxtossFi.js.map +0 -1
- package/dist/summary-report-DGp0-_XO.d.ts.map +0 -1
- package/dist/tools-BmuN627J.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summary-report-BEk8OFLs.js","names":["hi"],"sources":["../src/summary-report.ts"],"sourcesContent":["/**\n * Reporting helpers — production summaries and paper-quality figures — sit alongside `reporter.ts` rather\n * than replacing it.\n *\n * Three artefacts:\n *\n * - `summaryTable` Markdown table of per-candidate means,\n * 95% bootstrap CIs, BH-adjusted Wilcoxon\n * p-values, and Cohen's d versus a\n * comparator candidate.\n * - `paretoChart` Abstract spec for a cost vs quality\n * scatter, with gate decisions overlaid.\n * Returns numbers + labels — caller\n * chooses the plotting library.\n * - `gainHistogram`\n * Per-item paired holdout deltas as a\n * histogram spec (bins + counts + median +\n * CI). Same \"data, not images\" contract.\n *\n * The figure types are PlotSpecs — JSON-friendly, library-agnostic.\n * They aren't React components and they aren't PNGs; they are\n * what you'd hand to vega-lite, plotly, matplotlib, or your own\n * Canvas renderer to draw the actual figure.\n */\n\nimport type { GateDecision } from './held-out-gate'\nimport { pairRunRecords } from './paired-arms'\nimport type { FailureClusterReport } from './pipelines/failure-cluster'\nimport { canonicalize, hashJson } from './pre-registration'\nimport { observedSplitScore, type ScorePreference } from './rollout/reward'\nimport type { RunRecord } from './run-record'\nimport {\n benjaminiHochberg,\n confidenceInterval,\n pairedBootstrap,\n pairedCohensDz,\n pairedMde,\n wilcoxonSignedRank,\n} from './statistics'\n\n// ── summaryTable ───────────────────────────────────────────────────────\n\nexport interface SummaryTableOptions {\n /** Comparator candidate id. Wilcoxon + paired Cohen's dz are computed\n * versus this candidate. Required for paired stats columns. */\n comparator?: string\n /** Which split to read scores from. Default 'holdout'. */\n split?: 'search' | 'holdout'\n /** Confidence level for the bootstrap CI on the mean. Default 0.95. */\n confidence?: number\n /** FDR for BH adjustment of the comparison p-values. Default 0.05. */\n fdr?: number\n}\n\nexport interface SummaryTableRow {\n candidateId: string\n n: number\n mean: number\n ciLow: number\n ciHigh: number\n /** BH-adjusted q-value vs comparator, or null when unavailable. */\n qValue: number | null\n /** Paired Cohen's dz vs comparator, or null when the paired variance is zero. */\n cohensD: number | null\n /** Matched observations used for paired comparison, or null on the comparator row. */\n pairedN: number | null\n /** Candidate observations without a comparator match. */\n unpairedCandidateN: number | null\n /** Comparator observations without a candidate match. */\n unpairedComparatorN: number | null\n}\n\nexport interface SummaryTable {\n rows: SummaryTableRow[]\n comparator: string | null\n split: 'search' | 'holdout'\n /** Pre-rendered markdown — drop into a paper or PR. */\n markdown: string\n}\n\n/**\n * Table 1 helper. Buckets runs by `candidateId`, computes mean +\n * bootstrap CI on the chosen split, and (when a comparator is given)\n * BH-adjusted Wilcoxon p + paired Cohen's dz versus that comparator.\n */\nexport function summaryTable(runs: RunRecord[], opts: SummaryTableOptions = {}): SummaryTable {\n const split = opts.split ?? 'holdout'\n const confidence = opts.confidence ?? 0.95\n const fdr = opts.fdr ?? 0.05\n const comparator = opts.comparator ?? null\n\n const byCandidate = new Map<string, { runs: RunRecord[]; scores: number[] }>()\n for (const r of runs) {\n if (r.splitTag !== split) continue\n // RAW (`observedSplitScore`): every figure in this module reports what the\n // runs scored. Zeroing a gamed run here would erase it from the very table\n // a reviewer reads to notice it, and the split is already filtered above,\n // so the exact-split reader is the right one — no cross-split fallback.\n const v = observedSplitScore(r, split)\n if (typeof v !== 'number' || !Number.isFinite(v)) continue\n const bucket = byCandidate.get(r.candidateId) ?? { runs: [], scores: [] }\n bucket.runs.push(r)\n bucket.scores.push(v)\n byCandidate.set(r.candidateId, bucket)\n }\n\n const candidateIds = [...byCandidate.keys()].sort()\n const compRuns = comparator ? byCandidate.get(comparator) : undefined\n\n // First pass: per-candidate means + CIs + raw p-values.\n const tentative: Array<SummaryTableRow & { rawP: number | null }> = []\n for (const id of candidateIds) {\n const bucket = byCandidate.get(id)!\n const ci = confidenceInterval(bucket.scores, confidence)\n let rawP: number | null = null\n let d: number | null = null\n let pairedN: number | null = null\n let unpairedCandidateN: number | null = null\n let unpairedComparatorN: number | null = null\n if (comparator && compRuns && id !== comparator) {\n const paired = pairScoresByKey(bucket.runs, compRuns.runs, split)\n pairedN = paired.before.length\n unpairedCandidateN = paired.unpairedCandidateN\n unpairedComparatorN = paired.unpairedComparatorN\n if (paired.before.length >= 6) {\n rawP = wilcoxonSignedRank(paired.before, paired.after).p\n }\n d = pairedCohensDz(paired.before, paired.after)\n }\n tentative.push({\n candidateId: id,\n n: bucket.scores.length,\n mean: ci.mean,\n ciLow: ci.lower,\n ciHigh: ci.upper,\n qValue: rawP,\n cohensD: d,\n pairedN,\n unpairedCandidateN,\n unpairedComparatorN,\n rawP,\n })\n }\n\n // BH-adjust across the comparison set (skip NaN rows / the\n // comparator itself). Adjustment is a no-op when there are 0 or 1\n // comparators.\n if (comparator) {\n const idxs: number[] = []\n const ps: number[] = []\n for (let i = 0; i < tentative.length; i++) {\n const r = tentative[i]!\n if (r.candidateId === comparator) continue\n if (r.rawP === null || !Number.isFinite(r.rawP)) continue\n idxs.push(i)\n ps.push(r.rawP)\n }\n if (ps.length > 0) {\n const { qValues } = benjaminiHochberg(ps, fdr)\n for (let k = 0; k < idxs.length; k++) {\n tentative[idxs[k]!]!.qValue = qValues[k]!\n }\n }\n }\n\n const rows = tentative.map(({ rawP: _rawP, ...rest }) => rest)\n const markdown = renderSummaryTableMarkdown(rows, comparator, split)\n return { rows, comparator, split, markdown }\n}\n\nfunction pairScoresByKey(\n candidate: RunRecord[],\n baseline: RunRecord[],\n split: ScorePreference,\n): {\n before: number[]\n after: number[]\n unpairedCandidateN: number\n unpairedComparatorN: number\n} {\n // RAW, split-exact reads through `observedSplitScore` — the single allowed\n // spelling of the split-score derivation (see rollout/score-derivation-guard).\n const scoreOf = (run: RunRecord): number | undefined => {\n const v = observedSplitScore(run, split)\n return typeof v === 'number' && Number.isFinite(v) ? v : undefined\n }\n const scoredCandidate = candidate.filter((run) => scoreOf(run) !== undefined)\n const scoredBaseline = baseline.filter((run) => scoreOf(run) !== undefined)\n const pairing = pairRunRecords(scoredBaseline, scoredCandidate)\n return {\n before: pairing.pairs.map((pair) => scoreOf(pair.baseline) as number),\n after: pairing.pairs.map((pair) => scoreOf(pair.treatment) as number),\n unpairedCandidateN: pairing.unpairedTreatment.length,\n unpairedComparatorN: pairing.unpairedBaseline.length,\n }\n}\n\nfunction renderSummaryTableMarkdown(\n rows: SummaryTableRow[],\n comparator: string | null,\n split: 'search' | 'holdout',\n): string {\n const lines: string[] = []\n const cmpLabel = comparator ? ` (vs ${comparator})` : ''\n lines.push(`Summary Table — ${split} split${cmpLabel}`)\n lines.push('')\n lines.push(\"| Candidate | N | Pairs | Unpaired | Mean | 95% CI | q (BH) | Cohen's dz |\")\n lines.push('|---|---:|---:|---:|---:|---|---:|---:|')\n for (const r of rows) {\n const ci = `[${fmt(r.ciLow)}, ${fmt(r.ciHigh)}]`\n const q = r.qValue !== null ? r.qValue.toFixed(4) : '—'\n const d = r.cohensD !== null ? r.cohensD.toFixed(3) : '—'\n const paired = r.pairedN ?? '—'\n const unpaired =\n r.unpairedCandidateN === null || r.unpairedComparatorN === null\n ? '—'\n : `${r.unpairedCandidateN}/${r.unpairedComparatorN}`\n lines.push(\n `| ${r.candidateId} | ${r.n} | ${paired} | ${unpaired} | ${fmt(r.mean)} | ${ci} | ${q} | ${d} |`,\n )\n }\n return lines.join('\\n')\n}\n\n// ── paretoChart ─────────────────────────────────────────────────────\n\nexport interface ParetoPoint {\n candidateId: string\n /** Mean USD cost per run on the chosen split. */\n cost: number\n /** Mean score on the chosen split. */\n quality: number\n /** Number of runs that informed this point. */\n n: number\n /** Whether this candidate is on the Pareto frontier — high\n * quality, low cost, no dominator. */\n onFrontier: boolean\n /** Optional gate verdict for this candidate, if a `GateDecision`\n * for it was passed in. */\n gate?: 'promote' | 'reject'\n}\n\nexport interface ParetoFigureSpec {\n kind: 'pareto-cost-quality'\n split: 'search' | 'holdout'\n points: ParetoPoint[]\n axes: { x: 'costUsd'; y: 'score' }\n}\n\n/**\n * Cost vs quality scatter spec. `gateDecisions` is keyed by\n * candidate id; if present, every point picks up the gate verdict\n * for overlay.\n */\nexport function paretoChart(\n runs: RunRecord[],\n opts: {\n split?: 'search' | 'holdout'\n gateDecisions?: Record<string, GateDecision>\n } = {},\n): ParetoFigureSpec {\n const split = opts.split ?? 'holdout'\n\n const buckets = new Map<string, { cost: number[]; quality: number[] }>()\n for (const r of runs) {\n if (r.splitTag !== split) continue\n const v = observedSplitScore(r, split)\n if (typeof v !== 'number' || !Number.isFinite(v)) continue\n if (r.costUsd === null) continue\n const bucket = buckets.get(r.candidateId) ?? { cost: [], quality: [] }\n bucket.cost.push(r.costUsd)\n bucket.quality.push(v)\n buckets.set(r.candidateId, bucket)\n }\n\n const points: ParetoPoint[] = []\n for (const [candidateId, bucket] of buckets.entries()) {\n points.push({\n candidateId,\n cost: avg(bucket.cost),\n quality: avg(bucket.quality),\n n: bucket.cost.length,\n onFrontier: false,\n gate: opts.gateDecisions?.[candidateId]\n ? gateLabel(opts.gateDecisions[candidateId]!)\n : undefined,\n })\n }\n\n // Pareto: minimize cost, maximize quality. A point is dominated if\n // some other point has lower-or-equal cost AND higher-or-equal\n // quality with strict inequality somewhere.\n for (const p of points) {\n p.onFrontier = !points.some((q) => q !== p && dominates(q, p))\n }\n\n return {\n kind: 'pareto-cost-quality',\n split,\n axes: { x: 'costUsd', y: 'score' },\n points,\n }\n}\n\nfunction dominates(a: ParetoPoint, b: ParetoPoint): boolean {\n return a.cost <= b.cost && a.quality >= b.quality && (a.cost < b.cost || a.quality > b.quality)\n}\n\nfunction gateLabel(d: GateDecision): ParetoPoint['gate'] {\n return d.promote ? 'promote' : 'reject'\n}\n\n// ── gainHistogram ───────────────────────────────────────────\n\nexport interface GainDistributionBin {\n /** Inclusive lower edge. */\n lo: number\n /** Exclusive upper edge (or inclusive if it's the last bin). */\n hi: number\n /** Number of pairs whose delta lands in this bin. */\n count: number\n}\n\nexport interface GainDistributionFigureSpec {\n kind: 'gain-distribution'\n candidateId: string\n comparator: string\n split: 'search' | 'holdout'\n /** Number of pairs used. */\n n: number\n /** Candidate rows without a comparator match. */\n unpairedCandidateN: number\n /** Comparator rows without a candidate match. */\n unpairedComparatorN: number\n bins: GainDistributionBin[]\n median: number | null\n ci: { low: number; high: number } | null\n}\n\nexport interface GainDistributionOptions {\n /** Number of histogram bins. Default 11 (so the centre is exact at 0). */\n bins?: number\n /** Which split to use. Default 'holdout'. */\n split?: 'search' | 'holdout'\n /** Confidence level for the CI. Default 0.95. */\n confidence?: number\n /** Bootstrap resamples. Default 2000. */\n resamples?: number\n /** Deterministic seed. */\n seed?: number\n}\n\n/**\n * Held-out improvement distribution: per-pair delta (candidate −\n * comparator), histogrammed. Includes the bootstrap CI on the median\n * delta — same primitive the promotion gate uses.\n */\nexport function gainHistogram(\n runs: RunRecord[],\n candidateId: string,\n comparator: string,\n opts: GainDistributionOptions = {},\n): GainDistributionFigureSpec {\n const split = opts.split ?? 'holdout'\n const binCount = opts.bins ?? 11\n if (binCount < 1) throw new Error('gainHistogram: bins must be ≥ 1')\n\n const candidate = runs.filter((r) => r.candidateId === candidateId && r.splitTag === split)\n const baseline = runs.filter((r) => r.candidateId === comparator && r.splitTag === split)\n // pairScoresByKey returns before=baseline-score, after=candidate-score\n // for each canonical work-identity pair where both sides recorded a\n // valid score on this split. delta = after - before = candidate - baseline.\n const { before, after, unpairedCandidateN, unpairedComparatorN } = pairScoresByKey(\n candidate,\n baseline,\n split,\n )\n const n = before.length\n\n if (n === 0) {\n return {\n kind: 'gain-distribution',\n candidateId,\n comparator,\n split,\n n: 0,\n unpairedCandidateN,\n unpairedComparatorN,\n bins: [],\n median: null,\n ci: null,\n }\n }\n\n const deltas = before.map((b, i) => after[i]! - b)\n const sortedDeltas = [...deltas].sort((a, b) => a - b)\n const median = medianOfSorted(sortedDeltas)\n const min = sortedDeltas[0]!\n const max = sortedDeltas[sortedDeltas.length - 1]!\n\n // Symmetric bins around the wider of (|min|, |max|) so the chart\n // visually centres on zero without dropping outliers.\n const bound = Math.max(Math.abs(min), Math.abs(max), 1e-6)\n const lo = -bound\n const hi = bound\n const width = (hi - lo) / binCount\n const bins: GainDistributionBin[] = []\n for (let i = 0; i < binCount; i++) {\n bins.push({ lo: lo + i * width, hi: lo + (i + 1) * width, count: 0 })\n }\n for (const d of deltas) {\n let idx = Math.floor((d - lo) / width)\n if (idx < 0) idx = 0\n if (idx >= binCount) idx = binCount - 1\n bins[idx]!.count += 1\n }\n\n const ci = pairedBootstrap(before, after, {\n confidence: opts.confidence ?? 0.95,\n resamples: opts.resamples ?? 2000,\n statistic: 'median',\n seed: opts.seed,\n })\n\n return {\n kind: 'gain-distribution',\n candidateId,\n comparator,\n split,\n n,\n unpairedCandidateN,\n unpairedComparatorN,\n bins,\n median,\n ci: { low: ci.low, high: ci.high },\n }\n}\n\n// ── researchReport ───────────────────────────────────────────────────\n\nexport type ResearchReportDecision =\n | 'promote'\n | 'hold'\n | 'reject'\n | 'equivalent'\n | 'needs_more_data'\n\n/**\n * Hard floor below which a paired comparison is treated as uninformative\n * regardless of `minPairs`. Mirrors the lower limit on Wilcoxon signed-rank\n * exact tables; below this the test has no power to separate effect sizes.\n */\nexport const RESEARCH_REPORT_HARD_PAIR_FLOOR = 6\n\nexport interface ResearchReportOptions {\n /** Human-readable report title. */\n title?: string\n /** Comparator candidate id. Required for statistical decision guidance. */\n comparator?: string\n /** Which split to use for the primary decision. Default 'holdout'. */\n split?: 'search' | 'holdout'\n /** Confidence level used by lower-level report helpers. Default 0.95. */\n confidence?: number\n /** FDR threshold for q-values. Default 0.05. */\n fdr?: number\n /**\n * Soft floor on paired observations before issuing a directional\n * promote / reject. Below this we report `needs_more_data` and surface the\n * minimum detectable effect at the current N. Default 20 — chosen so the\n * Wilcoxon signed-rank approximation is reasonable and so the paired\n * bootstrap CI has non-degenerate coverage. Hard floor is enforced at\n * `RESEARCH_REPORT_HARD_PAIR_FLOOR` (6) regardless of this value.\n */\n minPairs?: number\n /**\n * Region of Practical Equivalence on the paired delta. When a candidate's\n * paired-delta CI is fully contained in `[low, high]`, the decision is\n * `equivalent` rather than `hold`. Sourced from the domain owner — there is\n * no statistically-defensible default.\n */\n rope?: { low: number; high: number }\n /**\n * Power for the minimum detectable effect (MDE) reported on each candidate.\n * Default 0.8.\n */\n mdePower?: number\n /**\n * Two-sided alpha for the MDE. Default matches `fdr` so the reported MDE\n * lines up with the test the report actually runs.\n */\n mdeAlpha?: number\n /** Optional held-out gate decisions keyed by candidate id. */\n gateDecisions?: Record<string, GateDecision>\n /** Optional failure clusters from failureClusterView. */\n failureClusters?: FailureClusterReport\n /** Build gain histograms for these candidates. Defaults to all non-comparator candidates. */\n candidateIds?: string[]\n /** Deterministic bootstrap seed passed to gainHistogram and the posterior helper. */\n seed?: number\n /** Report timestamp. Defaults to current time. */\n generatedAt?: string\n /**\n * Hash of a preregistered protocol (e.g. `signManifest({...}).contentHash`).\n * Embedded verbatim in the report so the analysis can be cited as the\n * preregistered one rather than a post-hoc fishing expedition.\n */\n preregistrationHash?: string\n}\n\nexport interface ResearchReportRecommendation {\n decision: ResearchReportDecision\n candidateId: string | null\n rationale: string[]\n risks: string[]\n nextActions: string[]\n}\n\nexport interface ResearchReportCandidate {\n candidateId: string\n n: number\n mean: number\n ciLow: number\n ciHigh: number\n qValue: number | null\n cohensD: number | null\n meanDeltaVsComparator: number | null\n pairedN: number\n medianGain: number | null\n meanGain: number | null\n gainCi: { low: number; high: number } | null\n /**\n * Bayesian-bootstrap posterior summaries on the paired mean delta.\n * Dirichlet(1, ..., 1) weights represent uncertainty over the empirical\n * distribution of matched deltas.\n */\n prGreaterThanZero: number | null\n prInRope: number | null\n /**\n * Minimum detectable effect (in score units) at the candidate's paired N,\n * the configured power, and the configured alpha. Standardised by the\n * observed paired-delta SD and inverted via `requiredSampleSize`. Reported\n * for every candidate so a `needs_more_data` verdict is actionable.\n */\n mde: number | null\n onParetoFrontier: boolean\n gate?: ParetoPoint['gate']\n decision: ResearchReportDecision\n decisionReason: string\n}\n\nexport interface ResearchReportMethodology {\n /**\n * Plain-language assumptions the report depends on. Read these first when\n * deciding whether the verdict is load-bearing for a launch decision.\n */\n assumptions: string[]\n /** Tests and estimators the verdict was computed from. */\n methods: string[]\n /** Alternatives the author considered and why this report didn't take them. */\n alternatives: string[]\n /** Failure modes — when this report should NOT drive a decision. */\n whenNotToApply: string[]\n /** Citations for the methodological choices above. */\n citations: string[]\n}\n\nexport interface ResearchReport {\n kind: 'agent-eval-research-report'\n title: string\n generatedAt: string\n split: 'search' | 'holdout'\n comparator: string | null\n /**\n * SHA-256 over the canonicalised set of `(runId, candidateId, split)` triples\n * the report was computed from, plus the comparator and split. Stable across\n * key insertion order; recomputable by the reader to verify provenance.\n */\n runFingerprint: string\n preregistrationHash: string | null\n rope: { low: number; high: number } | null\n executiveSummary: string[]\n recommendation: ResearchReportRecommendation\n candidates: ResearchReportCandidate[]\n summary: SummaryTable\n charts: {\n pareto: ParetoFigureSpec\n gains: GainDistributionFigureSpec[]\n }\n methodology: ResearchReportMethodology\n failureClusters?: FailureClusterReport\n markdown: string\n html: string\n}\n\n/**\n * Internal: paired posterior summary on (candidate − comparator) deltas.\n *\n * Returns the bootstrap CI on the median (matching `gainHistogram`) plus\n * Bayesian-bootstrap posterior summaries Pr(Δ>0) and Pr(Δ∈ROPE) computed\n * from Dirichlet(1, ..., 1) weights on the observed paired deltas\n * (Rubin 1981), and the\n * minimum detectable paired effect at the configured power and α.\n *\n * `null` is returned when no paired observations exist; callers must\n * gate on `n` before consuming the bootstrap statistics.\n */\nfunction pairedPosterior(\n runs: RunRecord[],\n candidateId: string,\n comparator: string,\n opts: {\n split: 'search' | 'holdout'\n confidence: number\n seed?: number\n rope: { low: number; high: number } | null\n mdePower: number\n mdeAlpha: number\n },\n): {\n n: number\n meanDelta: number\n medianDelta: number\n sdDelta: number\n ci: { low: number; high: number }\n prGreaterThanZero: number\n prInRope: number | null\n mde: number\n} | null {\n const candidate = runs.filter((r) => r.candidateId === candidateId && r.splitTag === opts.split)\n const baseline = runs.filter((r) => r.candidateId === comparator && r.splitTag === opts.split)\n const { before, after } = pairScoresByKey(candidate, baseline, opts.split)\n const n = before.length\n if (n === 0) return null\n\n const deltas = before.map((b, i) => after[i]! - b)\n const meanDelta = deltas.reduce((s, x) => s + x, 0) / n\n const sortedDeltas = [...deltas].sort((a, b) => a - b)\n const medianDelta = medianOfSorted(sortedDeltas)\n const sdDelta = stdev(deltas, meanDelta)\n\n const ci = pairedBootstrap(before, after, {\n confidence: opts.confidence,\n resamples: 2000,\n statistic: 'median',\n seed: opts.seed,\n })\n\n const meanSamples = bayesianBootstrapMeanSamples(deltas, 2000, opts.seed)\n const prGreaterThanZero =\n meanSamples.length === 0 ? 0 : meanSamples.filter((s) => s > 0).length / meanSamples.length\n const prInRope =\n opts.rope === null || meanSamples.length === 0\n ? null\n : meanSamples.filter((s) => s >= opts.rope!.low && s <= opts.rope!.high).length /\n meanSamples.length\n\n const dStandardised = pairedMde({ nPaired: n, alpha: opts.mdeAlpha, power: opts.mdePower })\n const mde = sdDelta === 0 ? 0 : dStandardised * sdDelta\n\n return {\n n,\n meanDelta,\n medianDelta,\n sdDelta,\n ci: { low: ci.low, high: ci.high },\n prGreaterThanZero,\n prInRope,\n mde,\n }\n}\n\nfunction bayesianBootstrapMeanSamples(\n deltas: number[],\n resamples: number,\n seed?: number,\n): number[] {\n const n = deltas.length\n if (n === 0) return []\n if (n === 1) return new Array<number>(resamples).fill(deltas[0]!)\n const rng = seedRng(seed)\n const samples = new Array<number>(resamples)\n for (let b = 0; b < resamples; b++) {\n let weightedSum = 0\n let weightSum = 0\n for (let k = 0; k < n; k++) {\n const weight = -Math.log(Math.max(Number.MIN_VALUE, rng()))\n weightedSum += deltas[k]! * weight\n weightSum += weight\n }\n samples[b] = weightedSum / weightSum\n }\n return samples\n}\n\nfunction seedRng(seed?: number): () => number {\n if (seed === undefined) return Math.random\n let s = seed >>> 0\n return () => {\n s = (s + 0x6d2b79f5) >>> 0\n let t = s\n t = Math.imul(t ^ (t >>> 15), t | 1)\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61)\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296\n }\n}\n\nfunction stdev(xs: number[], mean: number): number {\n if (xs.length < 2) return 0\n let sse = 0\n for (const x of xs) sse += (x - mean) ** 2\n return Math.sqrt(sse / (xs.length - 1))\n}\n\n/**\n * Executive research report for CPO / AI-lead / launch-review consumption.\n *\n * Composes:\n * - `summaryTable` marginal stats with BH-FDR-adjusted q-values\n * - `paretoChart` cost-vs-quality frontier with gate overlay\n * - `gainHistogram` per-candidate paired-delta distribution\n * - paired posterior (this file): bootstrap CI on median, Bayesian-bootstrap Pr(Δ>0),\n * Pr(Δ∈ROPE), MDE at the configured power\n *\n * Decisions are made on paired evidence — never on marginal means alone —\n * and respect any held-out gate decision the caller passes through. The\n * report embeds a SHA-256 fingerprint of the input run set and, optionally,\n * the hash of a preregistered protocol so a downstream reader can verify\n * provenance and that the analysis was the preregistered one.\n *\n * Async because the fingerprint uses Web Crypto via `hashJson`; deterministic\n * for any fixed `runs`, `seed`, and ROPE.\n */\nexport async function researchReport(\n runs: RunRecord[],\n opts: ResearchReportOptions = {},\n): Promise<ResearchReport> {\n const split = opts.split ?? 'holdout'\n const comparator = opts.comparator ?? null\n const confidence = opts.confidence ?? 0.95\n const fdr = opts.fdr ?? 0.05\n const minPairs = Math.max(opts.minPairs ?? 20, RESEARCH_REPORT_HARD_PAIR_FLOOR)\n const rope = opts.rope ?? null\n const mdePower = opts.mdePower ?? 0.8\n const mdeAlpha = opts.mdeAlpha ?? fdr\n const title = opts.title ?? 'Agent Evaluation Research Report'\n const generatedAt = opts.generatedAt ?? new Date().toISOString()\n const preregistrationHash = opts.preregistrationHash ?? null\n\n if (rope && !(Number.isFinite(rope.low) && Number.isFinite(rope.high) && rope.low <= rope.high)) {\n throw new Error(\n `researchReport: rope must satisfy low ≤ high with finite bounds, got ${JSON.stringify(rope)}`,\n )\n }\n\n const summary = summaryTable(runs, {\n comparator: comparator ?? undefined,\n split,\n confidence,\n fdr,\n })\n const pareto = paretoChart(runs, { split, gateDecisions: opts.gateDecisions })\n const candidateIds =\n opts.candidateIds ?? summary.rows.map((r) => r.candidateId).filter((id) => id !== comparator)\n const gains = comparator\n ? candidateIds.map((id) =>\n gainHistogram(runs, id, comparator, {\n split,\n confidence,\n seed: opts.seed,\n }),\n )\n : []\n\n const gainByCandidate = new Map(gains.map((g) => [g.candidateId, g]))\n const paretoByCandidate = new Map(pareto.points.map((p) => [p.candidateId, p]))\n const posteriorByCandidate = new Map<string, ReturnType<typeof pairedPosterior>>()\n if (comparator) {\n for (const id of candidateIds) {\n posteriorByCandidate.set(\n id,\n pairedPosterior(runs, id, comparator, {\n split,\n confidence,\n seed: opts.seed,\n rope,\n mdePower,\n mdeAlpha,\n }),\n )\n }\n }\n\n const candidates = summary.rows\n .map((row) => {\n const gain = gainByCandidate.get(row.candidateId)\n const point = paretoByCandidate.get(row.candidateId)\n const posterior = posteriorByCandidate.get(row.candidateId) ?? null\n const gateDecision = opts.gateDecisions?.[row.candidateId]\n const classified = classifyCandidate(row, {\n comparator,\n posterior,\n point,\n gateDecision,\n split,\n fdr,\n minPairs,\n rope,\n })\n return {\n candidateId: row.candidateId,\n n: row.n,\n mean: row.mean,\n ciLow: row.ciLow,\n ciHigh: row.ciHigh,\n qValue: row.qValue,\n cohensD: row.cohensD,\n meanDeltaVsComparator: posterior ? posterior.meanDelta : null,\n pairedN: posterior?.n ?? gain?.n ?? 0,\n medianGain: posterior ? posterior.medianDelta : gain ? gain.median : null,\n meanGain: posterior ? posterior.meanDelta : null,\n gainCi: posterior ? posterior.ci : gain ? gain.ci : null,\n prGreaterThanZero: posterior ? posterior.prGreaterThanZero : null,\n prInRope: posterior ? posterior.prInRope : null,\n mde: posterior ? posterior.mde : null,\n onParetoFrontier: point?.onFrontier ?? false,\n gate: gateDecision ? gateLabel(gateDecision) : undefined,\n decision: classified.decision,\n decisionReason: classified.reason,\n } satisfies ResearchReportCandidate\n })\n .sort((a, b) => {\n const decisionRank = decisionWeight(b.decision) - decisionWeight(a.decision)\n if (decisionRank !== 0) return decisionRank\n return b.mean - a.mean\n })\n\n const recommendation = buildRecommendation(candidates, {\n comparator,\n failureClusters: opts.failureClusters,\n rope,\n minPairs,\n preregistrationHash,\n })\n const executiveSummary = buildExecutiveSummary(candidates, recommendation, {\n comparator,\n split,\n failureClusters: opts.failureClusters,\n preregistrationHash,\n })\n const methodology = buildMethodology({\n split,\n comparator,\n fdr,\n minPairs,\n rope,\n confidence,\n mdePower,\n mdeAlpha,\n })\n\n const runFingerprint = await hashJson(\n canonicalize({\n triples: runs\n .filter((r) => r.splitTag === split)\n .map((r) => ({ runId: r.runId, candidateId: r.candidateId, splitTag: r.splitTag }))\n .sort((a, b) => a.runId.localeCompare(b.runId)),\n comparator,\n split,\n }),\n )\n\n const markdown = renderResearchMarkdown({\n title,\n generatedAt,\n split,\n comparator,\n rope,\n runFingerprint,\n preregistrationHash,\n executiveSummary,\n recommendation,\n candidates,\n summary,\n pareto,\n gains,\n methodology,\n failureClusters: opts.failureClusters,\n })\n const html = renderResearchHtml(markdown, title)\n\n return {\n kind: 'agent-eval-research-report',\n title,\n generatedAt,\n split,\n comparator,\n runFingerprint,\n preregistrationHash,\n rope,\n executiveSummary,\n recommendation,\n candidates,\n summary,\n charts: { pareto, gains },\n methodology,\n failureClusters: opts.failureClusters,\n markdown,\n html,\n }\n}\n\nfunction buildMethodology(ctx: {\n split: 'search' | 'holdout'\n comparator: string | null\n fdr: number\n minPairs: number\n rope: { low: number; high: number } | null\n confidence: number\n mdePower: number\n mdeAlpha: number\n}): ResearchReportMethodology {\n const assumptions: string[] = [\n 'Pairs are matched by (experimentId, scenarioId, seed); input order does not affect the comparison.',\n 'Paired deltas are exchangeable conditional on the matched scenario — no mid-run distribution shift.',\n `Decisions are pre-specified at fdr=${ctx.fdr}, minPairs=${ctx.minPairs}, confidence=${ctx.confidence}; deviating from these post-hoc invalidates the false-discovery control.`,\n ]\n if (ctx.rope) {\n assumptions.push(\n `The Region of Practical Equivalence ${formatRope(ctx.rope)} is supplied by the domain owner; equivalent verdicts are only meaningful if that range is treated as the standing definition of \"no material difference.\"`,\n )\n }\n if (ctx.comparator === null) {\n assumptions.push('No comparator was configured; this run is descriptive, not causal.')\n }\n const methods: string[] = [\n \"Marginal scores summarised with BH-FDR-adjusted Wilcoxon signed-rank q-values and paired Cohen's dz via summaryTable.\",\n 'Paired evidence summarised with a frequentist bootstrap CI on the median delta and a Dirichlet-weight Bayesian bootstrap for Pr(Δ>0) and Pr(Δ∈ROPE) on the mean delta.',\n `Minimum detectable effect reported per candidate at α=${ctx.mdeAlpha} (two-sided), power=${ctx.mdePower}, standardised by the observed paired-delta SD.`,\n 'Pareto frontier flagged as a separate axis (cost vs quality); a candidate can be on-frontier without winning the paired test.',\n 'Held-out gate decisions, when supplied, override the statistical verdict in the reject direction.',\n ]\n const alternatives: string[] = [\n 'Paired t-test rejected: not robust to the heavy-tailed score distributions common in agent benchmarks.',\n 'Unpaired Mann–Whitney rejected: matched scenarios make pairing free; unpaired throws away that variance reduction.',\n 'Sequential / always-valid inference (e-values, mSPRT) is the right tool for iterative sweeps and is out of scope for this single-look report — preregister and run once, or wrap this report in an alpha-spending schedule.',\n 'Hierarchical Bayesian shrinkage across many candidates is future work; the current ranking uses raw paired statistics.',\n ]\n const whenNotToApply: string[] = [\n `Paired N below ${RESEARCH_REPORT_HARD_PAIR_FLOOR} on any candidate — the bootstrap CI is degenerate.`,\n 'Comparator chosen post-hoc by inspecting the same data; q-values are no longer false-discovery-controlled.',\n 'Scenarios not drawn under a stable preregistered protocol; the report can describe the data but cannot anchor a launch decision.',\n 'Score distributions with mid-run shift (judge model swap, rubric change, infra outage) — pair exchangeability is violated.',\n ]\n const citations: string[] = [\n 'Benjamini, Y. & Hochberg, Y. (1995). Controlling the false discovery rate: a practical and powerful approach to multiple testing. JRSS B, 57(1), 289–300.',\n 'Wilcoxon, F. (1945). Individual comparisons by ranking methods. Biometrics Bulletin, 1(6), 80–83.',\n 'Efron, B. (1979). Bootstrap methods: another look at the jackknife. Annals of Statistics, 7(1), 1–26.',\n 'Rubin, D. B. (1981). The Bayesian bootstrap. Annals of Statistics, 9(1), 130–134.',\n 'Kruschke, J. K. (2018). Rejecting or accepting parameter values in Bayesian estimation. Advances in Methods and Practices in Psychological Science, 1(2), 270–280. (ROPE.)',\n ]\n return { assumptions, methods, alternatives, whenNotToApply, citations }\n}\n\nfunction formatRope(rope: { low: number; high: number }): string {\n return `[${fmt(rope.low)}, ${fmt(rope.high)}]`\n}\n\nfunction classifyCandidate(\n row: SummaryTableRow,\n ctx: {\n comparator: string | null\n posterior: ReturnType<typeof pairedPosterior> | null\n point?: ParetoPoint\n gateDecision?: GateDecision\n split: 'search' | 'holdout'\n fdr: number\n minPairs: number\n rope: { low: number; high: number } | null\n },\n): { decision: ResearchReportDecision; reason: string } {\n if (ctx.comparator && row.candidateId === ctx.comparator) {\n return { decision: 'hold', reason: 'Comparator baseline.' }\n }\n if (ctx.gateDecision && !ctx.gateDecision.promote) {\n const code = ctx.gateDecision.rejectionCode ?? 'rejected'\n return {\n decision: 'reject',\n reason: `Held-out gate returned ${code}: ${ctx.gateDecision.reason}`,\n }\n }\n if (!ctx.comparator) {\n return {\n decision: ctx.point?.onFrontier ? 'hold' : 'needs_more_data',\n reason:\n 'No comparator configured; report ranks candidates but cannot anchor a promotion call.',\n }\n }\n if (!ctx.posterior || ctx.posterior.n < RESEARCH_REPORT_HARD_PAIR_FLOOR) {\n return {\n decision: 'needs_more_data',\n reason: `Only ${ctx.posterior?.n ?? 0} paired observations; below hard floor of ${RESEARCH_REPORT_HARD_PAIR_FLOOR} for any paired inference.`,\n }\n }\n const ci = ctx.posterior.ci\n if (ctx.split === 'search') {\n return {\n decision: 'hold',\n reason:\n 'Search-split evidence is descriptive; release decisions require matched holdout runs.',\n }\n }\n // A ZERO-WIDTH interval cannot support any directional or equivalence claim:\n // every resample was identical, so it says nothing about how far the estimate\n // could be wrong. It is the shape that sits fully inside any ROPE containing\n // it (a fake \"practically equivalent\") and clears any threshold below it (a\n // fake \"promote\"). Both are an absence of evidence, so it is disqualified from\n // each claim rather than short-circuiting the whole classifier — the\n // sample-size and MDE diagnostics below are still the more useful answer when\n // they apply.\n const degenerate = !Number.isFinite(ci.low) || !Number.isFinite(ci.high) || ci.low === ci.high\n if (!degenerate && ctx.rope && ci.low >= ctx.rope.low && ci.high <= ctx.rope.high) {\n return {\n decision: 'equivalent',\n reason: `Paired-delta CI [${fmt(ci.low)}, ${fmt(ci.high)}] is fully inside ROPE ${formatRope(ctx.rope)}; candidate is practically equivalent to comparator.`,\n }\n }\n const gainPositive = !degenerate && ci.low > 0\n const gainNegative = !degenerate && ci.high < 0\n if (gainNegative) {\n return {\n decision: 'reject',\n reason: `Paired-delta CI [${fmt(ci.low)}, ${fmt(ci.high)}] lies entirely below zero.`,\n }\n }\n if (ctx.posterior.n < ctx.minPairs) {\n return {\n decision: 'needs_more_data',\n reason: `Only ${ctx.posterior.n} paired observations; minimum detectable effect at this N is ${fmt(ctx.posterior.mde)} score units (need ≥ ${ctx.minPairs} pairs to issue a directional verdict).`,\n }\n }\n if (degenerate) {\n return {\n decision: 'needs_more_data',\n reason: `Paired-delta CI [${fmt(ci.low)}, ${fmt(ci.high)}] has zero width — every resample was identical, so the interval carries no information about how far the estimate could be wrong and cannot support a promote or equivalence call.`,\n }\n }\n if (row.qValue !== null && row.qValue <= ctx.fdr && gainPositive) {\n return {\n decision: 'promote',\n reason: `BH-adjusted q=${fmt(row.qValue)} ≤ ${ctx.fdr} and paired-delta CI [${fmt(ci.low)}, ${fmt(ci.high)}] excludes zero; Pr(Δ>0)=${fmt(ctx.posterior.prGreaterThanZero)}.`,\n }\n }\n return {\n decision: 'hold',\n reason: `Pr(Δ>0)=${fmt(ctx.posterior.prGreaterThanZero)} but CI [${fmt(ci.low)}, ${fmt(ci.high)}] crosses zero; effect not decisive at fdr=${ctx.fdr}.`,\n }\n}\n\nfunction buildRecommendation(\n candidates: ResearchReportCandidate[],\n ctx: {\n comparator: string | null\n failureClusters?: FailureClusterReport\n rope: { low: number; high: number } | null\n minPairs: number\n preregistrationHash: string | null\n },\n): ResearchReportRecommendation {\n const nonComparator = candidates.filter((c) => c.candidateId !== ctx.comparator)\n const bestPromote = nonComparator.find((c) => c.decision === 'promote')\n const bestEquivalent = nonComparator.find((c) => c.decision === 'equivalent')\n const chosen = bestPromote ?? bestEquivalent ?? nonComparator[0] ?? null\n const decision: ResearchReportDecision = bestPromote\n ? 'promote'\n : nonComparator.some((c) => c.decision === 'needs_more_data')\n ? 'needs_more_data'\n : bestEquivalent\n ? 'equivalent'\n : nonComparator.some((c) => c.decision === 'hold')\n ? 'hold'\n : 'reject'\n\n const rationale: string[] = []\n const risks: string[] = []\n const nextActions: string[] = []\n\n if (chosen) {\n rationale.push(`${chosen.candidateId}: ${chosen.decisionReason}`)\n if (chosen.gainCi) {\n const probSummary =\n chosen.prGreaterThanZero !== null ? `, Pr(Δ>0)=${fmt(chosen.prGreaterThanZero)}` : ''\n rationale.push(\n `Median paired gain CI: [${fmt(chosen.gainCi.low)}, ${fmt(chosen.gainCi.high)}]${probSummary}.`,\n )\n }\n if (chosen.mde !== null && Number.isFinite(chosen.mde)) {\n rationale.push(`MDE at current paired N=${chosen.pairedN}: ${fmt(chosen.mde)} score units.`)\n }\n }\n if (!ctx.comparator) {\n risks.push('No comparator was configured; verdict is descriptive, not causal.')\n nextActions.push('Re-run with a stable comparator candidate for paired inference.')\n }\n if (!ctx.preregistrationHash) {\n risks.push(\n 'No preregistration hash supplied; readers cannot verify the analysis was specified before data inspection.',\n )\n nextActions.push(\n 'Sign a HypothesisManifest before the next sweep and pass `preregistrationHash` so the report cites it.',\n )\n }\n if (ctx.rope === null && nonComparator.length > 0) {\n risks.push(\n 'No ROPE configured; the report cannot distinguish \"equivalent\" from \"inconclusive\".',\n )\n nextActions.push(\n 'Define a domain-specific Region of Practical Equivalence and pass it to lock in the equivalence threshold.',\n )\n }\n const inconclusive = nonComparator.filter((c) => c.decision === 'needs_more_data')\n if (inconclusive.length > 0) {\n const worst = inconclusive.reduce((a, b) => (b.pairedN < a.pairedN ? b : a))\n risks.push(\n `${inconclusive.length} candidate(s) below soft floor (${ctx.minPairs} pairs); thinnest is ${worst.candidateId} with ${worst.pairedN}.`,\n )\n nextActions.push(\n `Collect at least ${ctx.minPairs - worst.pairedN} more matched holdout runs for ${worst.candidateId}.`,\n )\n }\n const rejected = nonComparator.filter((c) => c.decision === 'reject')\n if (rejected.length > 0) {\n risks.push(\n `${rejected.length} candidate(s) failed the paired test or held-out gate; do not ship those variants.`,\n )\n }\n if (ctx.failureClusters && ctx.failureClusters.clusters.length > 0) {\n const top = ctx.failureClusters.clusters[0]!\n risks.push(`Top failure cluster: ${top.failureClass} across ${top.runCount} run(s).`)\n nextActions.push('Prioritize the largest failure cluster before broad rollout.')\n }\n if (decision === 'promote') {\n nextActions.push('Ship behind the existing promotion gate and monitor canaries.')\n } else if (decision === 'hold') {\n nextActions.push('Keep current production candidate while expanding holdout evidence.')\n } else if (decision === 'equivalent') {\n nextActions.push(\n 'Either keep the comparator (no quality regression) or promote on cost/latency grounds — equivalence does not justify either; the choice is a product decision, not a stats one.',\n )\n } else if (decision === 'reject') {\n nextActions.push(\n 'Do not promote this sweep; inspect failures and generate a revised candidate.',\n )\n }\n\n return {\n decision,\n candidateId: chosen?.candidateId ?? null,\n rationale,\n risks,\n nextActions,\n }\n}\n\nfunction buildExecutiveSummary(\n candidates: ResearchReportCandidate[],\n recommendation: ResearchReportRecommendation,\n ctx: {\n comparator: string | null\n split: 'search' | 'holdout'\n failureClusters?: FailureClusterReport\n preregistrationHash: string | null\n },\n): string[] {\n const lines: string[] = []\n const nonComparator = candidates.filter((c) => c.candidateId !== ctx.comparator)\n lines.push(\n `Evaluated ${nonComparator.length} candidate(s) on the ${ctx.split} split${ctx.comparator ? ` against ${ctx.comparator}` : ''}.`,\n )\n lines.push(\n `Recommendation: ${recommendation.decision}${recommendation.candidateId ? ` ${recommendation.candidateId}` : ''}.`,\n )\n const promoted = nonComparator.filter((c) => c.decision === 'promote').length\n const held = nonComparator.filter((c) => c.decision === 'hold').length\n const equivalent = nonComparator.filter((c) => c.decision === 'equivalent').length\n const rejected = nonComparator.filter((c) => c.decision === 'reject').length\n const more = nonComparator.filter((c) => c.decision === 'needs_more_data').length\n lines.push(\n `Decision mix: ${promoted} promote, ${equivalent} equivalent, ${held} hold, ${rejected} reject, ${more} need more data.`,\n )\n const frontier = nonComparator.filter((c) => c.onParetoFrontier).map((c) => c.candidateId)\n if (frontier.length > 0) lines.push(`Pareto-frontier candidates: ${frontier.join(', ')}.`)\n if (ctx.failureClusters) {\n lines.push(\n `Failure clustering found ${ctx.failureClusters.totalFailures}/${ctx.failureClusters.totalRuns} failed runs across ${ctx.failureClusters.clusters.length} reportable cluster(s).`,\n )\n }\n lines.push(\n ctx.preregistrationHash\n ? `Preregistered analysis: ${ctx.preregistrationHash.slice(0, 12)}…`\n : 'Analysis is post-hoc — no preregistration hash supplied.',\n )\n return lines\n}\n\nfunction renderResearchMarkdown(report: {\n title: string\n generatedAt: string\n split: 'search' | 'holdout'\n comparator: string | null\n executiveSummary: string[]\n recommendation: ResearchReportRecommendation\n candidates: ResearchReportCandidate[]\n summary: SummaryTable\n pareto: ParetoFigureSpec\n gains: GainDistributionFigureSpec[]\n rope: { low: number; high: number } | null\n runFingerprint: string\n preregistrationHash: string | null\n methodology: ResearchReportMethodology\n failureClusters?: FailureClusterReport\n}): string {\n const lines: string[] = []\n lines.push(`# ${report.title}`)\n lines.push('')\n lines.push(`**Generated:** ${report.generatedAt}`)\n lines.push(`**Primary split:** ${report.split}`)\n lines.push(`**Comparator:** ${report.comparator ?? 'not configured'}`)\n lines.push(`**ROPE:** ${report.rope ? formatRope(report.rope) : 'not configured'}`)\n lines.push(`**Run fingerprint:** \\`${report.runFingerprint}\\``)\n lines.push(\n `**Preregistration:** ${report.preregistrationHash ? `\\`${report.preregistrationHash}\\`` : 'none'}`,\n )\n lines.push('')\n lines.push('## Executive Summary')\n lines.push('')\n for (const item of report.executiveSummary) lines.push(`- ${item}`)\n lines.push('')\n lines.push('## Recommendation')\n lines.push('')\n lines.push(`**Decision:** ${report.recommendation.decision}`)\n lines.push(`**Candidate:** ${report.recommendation.candidateId ?? 'N/A'}`)\n lines.push('')\n lines.push('### Rationale')\n lines.push('')\n for (const item of report.recommendation.rationale) lines.push(`- ${item}`)\n lines.push('')\n lines.push('### Risks')\n lines.push('')\n for (const item of report.recommendation.risks.length\n ? report.recommendation.risks\n : ['No material report-level risks detected.']) {\n lines.push(`- ${item}`)\n }\n lines.push('')\n lines.push('### Next Actions')\n lines.push('')\n for (const item of report.recommendation.nextActions) lines.push(`- ${item}`)\n lines.push('')\n lines.push('## Candidate Decision Table')\n lines.push('')\n lines.push(\n '| Candidate | Decision | Mean | Δ̄ | Pr(Δ>0) | q | dz | Paired N | Median Gain CI | MDE | Pareto | Gate |',\n )\n lines.push('|---|---|---:|---:|---:|---:|---:|---:|---|---:|---|---|')\n for (const c of report.candidates) {\n const delta = c.meanDeltaVsComparator === null ? '-' : signed(c.meanDeltaVsComparator)\n const prGt = c.prGreaterThanZero === null ? '-' : c.prGreaterThanZero.toFixed(3)\n const q = c.qValue !== null ? c.qValue.toFixed(4) : '-'\n const d = c.cohensD !== null ? c.cohensD.toFixed(3) : '-'\n const gain = c.gainCi ? `[${fmt(c.gainCi.low)}, ${fmt(c.gainCi.high)}]` : '-'\n const mde = c.mde === null || !Number.isFinite(c.mde) ? '-' : fmt(c.mde)\n lines.push(\n `| ${c.candidateId} | ${c.decision} | ${fmt(c.mean)} | ${delta} | ${prGt} | ${q} | ${d} | ${c.pairedN} | ${gain} | ${mde} | ${c.onParetoFrontier ? 'yes' : 'no'} | ${c.gate ?? '-'} |`,\n )\n }\n lines.push('')\n lines.push('## Statistical Summary')\n lines.push('')\n lines.push(report.summary.markdown)\n lines.push('')\n lines.push('## Methodology')\n lines.push('')\n lines.push('### Assumptions')\n lines.push('')\n for (const item of report.methodology.assumptions) lines.push(`- ${item}`)\n lines.push('')\n lines.push('### Methods')\n lines.push('')\n for (const item of report.methodology.methods) lines.push(`- ${item}`)\n lines.push('')\n lines.push('### Alternatives Considered')\n lines.push('')\n for (const item of report.methodology.alternatives) lines.push(`- ${item}`)\n lines.push('')\n lines.push('### When NOT To Apply')\n lines.push('')\n for (const item of report.methodology.whenNotToApply) lines.push(`- ${item}`)\n lines.push('')\n lines.push('### Citations')\n lines.push('')\n for (const item of report.methodology.citations) lines.push(`- ${item}`)\n lines.push('')\n lines.push('## Chart Specs')\n lines.push('')\n lines.push(\n 'The report carries JSON chart specs for Pareto cost/quality and paired gain histograms.',\n )\n lines.push('')\n lines.push('```json')\n lines.push(JSON.stringify({ pareto: report.pareto, gains: report.gains }, null, 2))\n lines.push('```')\n if (report.failureClusters) {\n lines.push('')\n lines.push('## Failure Clusters')\n lines.push('')\n lines.push('| Failure Class | Runs | Scenarios | Tool | Example |')\n lines.push('|---|---:|---:|---|---|')\n for (const c of report.failureClusters.clusters.slice(0, 10)) {\n lines.push(\n `| ${c.failureClass} | ${c.runCount} | ${c.scenarioIds.length} | ${c.toolName ?? '-'} | ${escapePipes(c.exampleError ?? c.exampleRunId)} |`,\n )\n }\n }\n return lines.join('\\n')\n}\n\nfunction renderResearchHtml(markdown: string, title: string): string {\n const body = markdownToHtml(markdown)\n return [\n '<!doctype html>',\n '<html lang=\"en\">',\n '<head>',\n '<meta charset=\"utf-8\">',\n '<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">',\n `<title>${escapeHtml(title)}</title>`,\n '<style>',\n 'body{font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif;margin:0;color:#172026;background:#f7f8f8;}',\n 'main{max-width:1080px;margin:0 auto;padding:40px 24px 64px;background:#fff;min-height:100vh;}',\n 'h1{font-size:34px;line-height:1.15;margin:0 0 20px;}h2{margin-top:34px;border-top:1px solid #d9dfdf;padding-top:22px;}h3{margin-top:22px;}',\n 'p,li{line-height:1.55;}table{border-collapse:collapse;width:100%;margin:16px 0;font-size:14px;}th,td{border:1px solid #d9dfdf;padding:8px;text-align:left;}th{background:#eef2f2;}',\n 'code,pre{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;}pre{overflow:auto;background:#111827;color:#f9fafb;padding:16px;border-radius:6px;}',\n '</style>',\n '</head>',\n '<body><main>',\n body,\n '</main></body></html>',\n ].join('\\n')\n}\n\nfunction markdownToHtml(markdown: string): string {\n const lines = markdown.split('\\n')\n const html: string[] = []\n let inList = false\n let inCode = false\n let code: string[] = []\n let table: string[] = []\n\n const flushList = () => {\n if (inList) {\n html.push('</ul>')\n inList = false\n }\n }\n const flushTable = () => {\n if (table.length === 0) return\n html.push(renderMarkdownTable(table))\n table = []\n }\n\n for (const line of lines) {\n if (line.startsWith('```')) {\n if (inCode) {\n html.push(`<pre><code>${escapeHtml(code.join('\\n'))}</code></pre>`)\n code = []\n inCode = false\n } else {\n flushList()\n flushTable()\n inCode = true\n }\n continue\n }\n if (inCode) {\n code.push(line)\n continue\n }\n if (line.startsWith('|')) {\n flushList()\n table.push(line)\n continue\n }\n flushTable()\n if (line.startsWith('- ')) {\n if (!inList) {\n html.push('<ul>')\n inList = true\n }\n html.push(`<li>${inlineMarkdown(line.slice(2))}</li>`)\n continue\n }\n flushList()\n if (line.startsWith('# ')) html.push(`<h1>${inlineMarkdown(line.slice(2))}</h1>`)\n else if (line.startsWith('## ')) html.push(`<h2>${inlineMarkdown(line.slice(3))}</h2>`)\n else if (line.startsWith('### ')) html.push(`<h3>${inlineMarkdown(line.slice(4))}</h3>`)\n else if (line.trim() === '') html.push('')\n else html.push(`<p>${inlineMarkdown(line)}</p>`)\n }\n flushList()\n flushTable()\n return html.join('\\n')\n}\n\nfunction renderMarkdownTable(lines: string[]): string {\n const rows = lines\n .filter((line) => !/^\\|[-:\\s|]+\\|$/.test(line))\n .map((line) =>\n line\n .slice(1, -1)\n .split('|')\n .map((cell) => inlineMarkdown(cell.trim())),\n )\n if (rows.length === 0) return ''\n const [head, ...body] = rows\n const th = head!.map((cell) => `<th>${cell}</th>`).join('')\n const trs = body\n .map((row) => `<tr>${row.map((cell) => `<td>${cell}</td>`).join('')}</tr>`)\n .join('\\n')\n return `<table><thead><tr>${th}</tr></thead><tbody>${trs}</tbody></table>`\n}\n\nfunction inlineMarkdown(s: string): string {\n return escapeHtml(s).replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>')\n}\n\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n}\n\nfunction escapePipes(s: string): string {\n return s.replace(/\\|/g, '\\\\|')\n}\n\nfunction decisionWeight(decision: ResearchReportDecision): number {\n if (decision === 'promote') return 5\n if (decision === 'equivalent') return 4\n if (decision === 'hold') return 3\n if (decision === 'needs_more_data') return 2\n return 1\n}\n\nfunction signed(x: number): string {\n return `${x >= 0 ? '+' : ''}${fmt(x)}`\n}\n\n// ── tiny helpers ─────────────────────────────────────────────────────\n\nfunction avg(xs: number[]): number {\n if (xs.length === 0) return Number.NaN\n return xs.reduce((s, x) => s + x, 0) / xs.length\n}\n\nfunction medianOfSorted(sorted: number[]): number {\n if (sorted.length === 0) return 0\n const mid = Math.floor(sorted.length / 2)\n return sorted.length % 2 === 0 ? (sorted[mid - 1]! + sorted[mid]!) / 2 : sorted[mid]!\n}\n\nfunction fmt(x: number): string {\n if (!Number.isFinite(x)) return String(x)\n return x.toFixed(4)\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,117 @@
|
|
|
1
1
|
import { a as RunRecord } from "./run-record-DcObtIGh.js";
|
|
2
|
+
import { y as PairedBootstrapResult } from "./statistics-CKOqre5S.js";
|
|
2
3
|
import { n as FailureClusterReport } from "./failure-cluster-CqcvCcdR.js";
|
|
4
|
+
//#region src/paired-promotion-decision.d.ts
|
|
5
|
+
/** Which paired estimator produced the deciding interval. */
|
|
6
|
+
type PairedDecisionStatistic = 'paired_risk_difference' | 'mean_bootstrap' | 'median_bootstrap';
|
|
7
|
+
/** Which test carried the decision, given the estimator and the sample size. */
|
|
8
|
+
type PairedDecisionMethod = 'score-interval' | 'bootstrap-ci' | 'exact-sign';
|
|
9
|
+
/** McNemar's exact paired-binary evidence, on the two-point path only. */
|
|
10
|
+
interface PairedMcNemarEvidence {
|
|
11
|
+
/** Discordant pairs the treatment won. */
|
|
12
|
+
b: number;
|
|
13
|
+
/** Discordant pairs the control won. */
|
|
14
|
+
c: number;
|
|
15
|
+
/** b + c — the only pairs carrying information. */
|
|
16
|
+
nDiscordant: number;
|
|
17
|
+
/** Two-sided exact p-value. */
|
|
18
|
+
pValue: number;
|
|
19
|
+
}
|
|
20
|
+
interface PairedPromotionDecisionOptions {
|
|
21
|
+
/** Smallest candidate-minus-baseline delta that counts as improvement, in the
|
|
22
|
+
* caller's native units. May be negative (a noninferiority margin). Default 0. */
|
|
23
|
+
threshold?: number;
|
|
24
|
+
/** Confidence level. Default 0.95. */
|
|
25
|
+
confidence?: number;
|
|
26
|
+
/** Bootstrap resamples, on the paths where a bootstrap decides. Default 2000. */
|
|
27
|
+
resamples?: number;
|
|
28
|
+
/** Deterministic bootstrap seed. Omitted ⇒ derived from the deltas. */
|
|
29
|
+
seed?: number;
|
|
30
|
+
/** Caller-required paired observations. The exact test may impose a higher
|
|
31
|
+
* minimum; the effective one is reported as `minimumPairs`. */
|
|
32
|
+
minPairs?: number;
|
|
33
|
+
/**
|
|
34
|
+
* `'mean'` (default) routes by SHAPE: a two-point (pass/fail) outcome on any
|
|
35
|
+
* encoding decides on the score interval, everything else on the mean
|
|
36
|
+
* bootstrap. `'median'` forces the median bootstrap on every input, including
|
|
37
|
+
* shapes where it is structurally blind — kept for callers who want outlier
|
|
38
|
+
* robustness on genuinely continuous outcomes and accept that cost.
|
|
39
|
+
*/
|
|
40
|
+
statistic?: 'mean' | 'median';
|
|
41
|
+
}
|
|
42
|
+
interface PairedPromotionDecision {
|
|
43
|
+
/** Paired observations supplied. */
|
|
44
|
+
n: number;
|
|
45
|
+
/** Threshold the interval was judged against, native units. */
|
|
46
|
+
threshold: number;
|
|
47
|
+
confidence: number;
|
|
48
|
+
statistic: PairedDecisionStatistic;
|
|
49
|
+
method: PairedDecisionMethod;
|
|
50
|
+
/** Common positive level of a two-point outcome ({0,1} ⇒ 1, {0,100} ⇒ 100),
|
|
51
|
+
* or null when the outcome is not two-point. Non-null is exactly the
|
|
52
|
+
* condition for the `paired_risk_difference` path, and it is the factor
|
|
53
|
+
* `delta` / `low` / `high` were rescaled by. */
|
|
54
|
+
binaryScale: number | null;
|
|
55
|
+
/** Exact-tie fraction over the paired deltas; null when there are no pairs. */
|
|
56
|
+
tieFraction: number | null;
|
|
57
|
+
/** Point estimate of the DECIDING statistic, in the caller's native units. */
|
|
58
|
+
delta: number;
|
|
59
|
+
/** Lower bound of the DECIDING interval, native units. */
|
|
60
|
+
low: number;
|
|
61
|
+
/** Upper bound of the DECIDING interval, native units. */
|
|
62
|
+
high: number;
|
|
63
|
+
/** The bootstrap that decided, or null when the score interval did. Callers
|
|
64
|
+
* that need a bootstrap as a diagnostic on the two-point path compute their
|
|
65
|
+
* own — it is not computed here, so the binary path costs no resamples. */
|
|
66
|
+
bootstrap: PairedBootstrapResult | null;
|
|
67
|
+
/** McNemar's exact evidence, or null off the two-point path. */
|
|
68
|
+
mcnemar: PairedMcNemarEvidence | null;
|
|
69
|
+
/** Exact one-sided sign-test p-value on the small-sample bootstrap path;
|
|
70
|
+
* null otherwise. */
|
|
71
|
+
pValue: number | null;
|
|
72
|
+
/** Effective observation minimum after accounting for confidence. */
|
|
73
|
+
minimumPairs: number;
|
|
74
|
+
/** n >= minimumPairs. */
|
|
75
|
+
sufficient: boolean;
|
|
76
|
+
/** The deciding interval is zero-width or non-finite — no evidence in either
|
|
77
|
+
* direction, so it cannot clear any threshold on evidence. */
|
|
78
|
+
indeterminate: boolean;
|
|
79
|
+
/** McNemar's exact test refuses at a non-negative threshold. */
|
|
80
|
+
exactTestVetoes: boolean;
|
|
81
|
+
/** The deciding interval clears the threshold, ignoring the other two guards. */
|
|
82
|
+
clearsThreshold: boolean;
|
|
83
|
+
/** `sufficient && !indeterminate && clearsThreshold && !exactTestVetoes` —
|
|
84
|
+
* the whole rule. */
|
|
85
|
+
promote: boolean;
|
|
86
|
+
/** What `delta` measures, for a reason string. */
|
|
87
|
+
label: 'success-rate' | 'mean' | 'median';
|
|
88
|
+
/** Why a zero-width interval is zero-width; empty when it is not. */
|
|
89
|
+
indeterminateCause: string;
|
|
90
|
+
/** Sentence naming the test when the exact sign test decided; else empty. */
|
|
91
|
+
methodDetail: string;
|
|
92
|
+
}
|
|
93
|
+
/** The shape facts that pick the estimator, without computing an interval. */
|
|
94
|
+
interface PairedDecisionShape {
|
|
95
|
+
statistic: PairedDecisionStatistic;
|
|
96
|
+
/** Common positive level of a two-point outcome; null when not two-point. */
|
|
97
|
+
binaryScale: number | null;
|
|
98
|
+
/** Exact-tie fraction over the paired deltas; null when there are no pairs. */
|
|
99
|
+
tieFraction: number | null;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Which estimator {@link decidePairedPromotion} would use on this data, and the
|
|
103
|
+
* shape facts behind it — for callers that must report the shape on a path
|
|
104
|
+
* where no interval is computed at all (an early rejection, or zero pairs).
|
|
105
|
+
* Cheap: no bootstrap, no interval.
|
|
106
|
+
*/
|
|
107
|
+
declare function pairedDecisionShape(before: number[], after: number[], statistic?: 'mean' | 'median'): PairedDecisionShape;
|
|
108
|
+
/**
|
|
109
|
+
* Decide whether a paired candidate-minus-baseline delta clears a promotion
|
|
110
|
+
* threshold. `before` is the baseline arm, `after` the candidate arm, paired by
|
|
111
|
+
* position. Throws on unequal lengths.
|
|
112
|
+
*/
|
|
113
|
+
declare function decidePairedPromotion(before: number[], after: number[], options?: PairedPromotionDecisionOptions): PairedPromotionDecision;
|
|
114
|
+
//#endregion
|
|
3
115
|
//#region src/held-out-gate.d.ts
|
|
4
116
|
type HeldOutGateRejectionCode = 'few_runs' | 'incomplete_coverage' | 'missing_split_scores' | 'missing_cost' | 'negative_delta' | 'indeterminate_delta' | 'overfit_gap' | 'cost_ceiling';
|
|
5
117
|
/**
|
|
@@ -32,9 +144,22 @@ interface HeldOutGateConfig {
|
|
|
32
144
|
* required before promotion. The exact small-sample test may require more
|
|
33
145
|
* observations at the selected confidence. */
|
|
34
146
|
minProductiveRuns?: number;
|
|
35
|
-
/** The
|
|
36
|
-
*
|
|
147
|
+
/** The CI lower bound on the paired holdout delta must exceed this to
|
|
148
|
+
* promote. Read in the same units as the holdout scores: with binary
|
|
149
|
+
* (0/1) outcomes that is a change in success RATE (0.05 = +5pp).
|
|
150
|
+
* Default 0. */
|
|
37
151
|
pairedDeltaThreshold?: number;
|
|
152
|
+
/**
|
|
153
|
+
* Which paired statistic carries the promotion CI.
|
|
154
|
+
*
|
|
155
|
+
* `'auto'` (default) picks by outcome shape — two-point outcomes on the exact
|
|
156
|
+
* paired risk difference, everything else on the mean bootstrap (see the
|
|
157
|
+
* module header). `'median'` forces the median bootstrap on every input,
|
|
158
|
+
* including shapes where it is structurally blind and will refuse a real lift
|
|
159
|
+
* and accept a real regression. Set it for outlier robustness on genuinely
|
|
160
|
+
* continuous outcomes, or to reproduce a pre-0.134 verdict.
|
|
161
|
+
*/
|
|
162
|
+
deltaStatistic?: 'auto' | 'median';
|
|
38
163
|
/** Maximum allowed worsening of (search − holdout) gap relative to
|
|
39
164
|
* baseline. Default 0.15 (i.e. candidate may overfit by up to 15
|
|
40
165
|
* absolute score points more than baseline before rejection). */
|
|
@@ -80,6 +205,9 @@ interface HeldOutGateConfig {
|
|
|
80
205
|
*/
|
|
81
206
|
costPerTaskCeiling?: number;
|
|
82
207
|
}
|
|
208
|
+
/** Which paired statistic a `HeldOutGate` verdict was decided on. One
|
|
209
|
+
* definition, shared with every other gate that decides a paired interval. */
|
|
210
|
+
type DeltaStatistic = PairedDecisionStatistic;
|
|
83
211
|
interface GateEvidence {
|
|
84
212
|
/** Number of paired (candidate, baseline) holdout observations used. */
|
|
85
213
|
productiveRuns: number;
|
|
@@ -100,15 +228,54 @@ interface GateEvidence {
|
|
|
100
228
|
* `searchScore`, and therefore behind both overfit gaps.
|
|
101
229
|
*/
|
|
102
230
|
searchCoverage: SplitCoverage;
|
|
103
|
-
/** Median of paired holdout deltas, or null when there are no pairs.
|
|
231
|
+
/** Median of paired holdout deltas, or null when there are no pairs.
|
|
232
|
+
* ALWAYS the literal median — a diagnostic, not necessarily the statistic
|
|
233
|
+
* the verdict keyed on (see `deltaStatistic` / `decidingDelta`). On binary
|
|
234
|
+
* outcomes this is normally 0 by construction. */
|
|
104
235
|
medianPairedDelta: number | null;
|
|
105
|
-
/**
|
|
236
|
+
/** Which paired statistic the promotion CI was computed on, and therefore
|
|
237
|
+
* what `decidingDelta` / `pairedCI` mean. `'median_bootstrap'` = bootstrap
|
|
238
|
+
* CI on the median paired delta (continuous outcomes).
|
|
239
|
+
* `'mean_bootstrap'` = bootstrap CI on the mean paired delta, used when ties
|
|
240
|
+
* dominate and pin the median at 0. `'paired_risk_difference'` = change in
|
|
241
|
+
* success rate on a two-point (pass/fail) outcome, on an EXACT conditional
|
|
242
|
+
* interval dual to McNemar's exact test, rescaled to the outcome's native
|
|
243
|
+
* units (so a 0-100 dimension reports points, not a rate). */
|
|
244
|
+
deltaStatistic: DeltaStatistic;
|
|
245
|
+
/** Point estimate of `deltaStatistic` — the number the `pairedDeltaThreshold`
|
|
246
|
+
* check is about. Null when no CI was computed. */
|
|
247
|
+
decidingDelta: number | null;
|
|
248
|
+
/** CI on `deltaStatistic`, if computed. The gate promotes iff
|
|
249
|
+
* `pairedCI.low > pairedDeltaThreshold`. */
|
|
106
250
|
pairedCI: {
|
|
107
251
|
low: number;
|
|
108
252
|
high: number;
|
|
109
253
|
} | null;
|
|
110
|
-
/** Wilcoxon signed-rank p-value, if computed.
|
|
254
|
+
/** Wilcoxon signed-rank p-value, if computed. Always the Wilcoxon, on both
|
|
255
|
+
* paths, so this field never changes meaning. */
|
|
111
256
|
pairedPValue: number | null;
|
|
257
|
+
/** McNemar's exact paired-binary test on the holdout pairs, or null when the
|
|
258
|
+
* outcomes were not two-point (or no CI was computed). `b` = candidate-only
|
|
259
|
+
* wins, `c` = baseline-only wins; only these discordant pairs carry signal.
|
|
260
|
+
* When present it is not merely reported: the promotion interval is dual to
|
|
261
|
+
* this p-value, so a promotion at a non-negative threshold implies
|
|
262
|
+
* `pValue < 1 − confidence`. */
|
|
263
|
+
mcnemar: {
|
|
264
|
+
b: number;
|
|
265
|
+
c: number;
|
|
266
|
+
nDiscordant: number;
|
|
267
|
+
pValue: number;
|
|
268
|
+
} | null;
|
|
269
|
+
/** The common positive level of a two-point outcome — 1 for {0,1}, 100 for a
|
|
270
|
+
* 0-100 pass/fail dimension — or null when the outcome is not two-point.
|
|
271
|
+
* Non-null is exactly the condition for the `paired_risk_difference` path,
|
|
272
|
+
* and it is the factor `decidingDelta` / `pairedCI` were rescaled by. */
|
|
273
|
+
binaryScale: number | null;
|
|
274
|
+
/** Fraction of paired holdout deltas that are exact ties, or null when there
|
|
275
|
+
* are no pairs. At or above 0.5 the median paired delta is pinned at 0 by
|
|
276
|
+
* construction; this is the diagnostic for why a median-decided verdict
|
|
277
|
+
* (`deltaStatistic: 'median'`) saw nothing. */
|
|
278
|
+
tieFraction: number | null;
|
|
112
279
|
/** Mean candidate score on the search split, or null when absent. */
|
|
113
280
|
searchScore: number | null;
|
|
114
281
|
/** Mean candidate score on the holdout split, or null when absent. */
|
|
@@ -162,11 +329,19 @@ declare class HeldOutGate {
|
|
|
162
329
|
private readonly seed?;
|
|
163
330
|
private readonly costPerTaskCeiling?;
|
|
164
331
|
private readonly minCoverage;
|
|
332
|
+
private readonly deltaStatistic;
|
|
165
333
|
constructor(config: HeldOutGateConfig);
|
|
166
334
|
/** Decide whether `candidate` should replace `baseline`.
|
|
167
335
|
* Pairing is by `(experimentId, scenarioId, seed)`.
|
|
168
336
|
* Missing or duplicate identities throw instead of comparing by position. */
|
|
169
337
|
evaluate(candidate: RunRecord[], baseline: RunRecord[]): GateDecision;
|
|
338
|
+
/**
|
|
339
|
+
* Which paired statistic decides this data, and the shape facts behind it —
|
|
340
|
+
* for the early exits, where no interval is computed. Delegates to
|
|
341
|
+
* {@link pairedDecisionShape} so the reported shape can never disagree with
|
|
342
|
+
* the shape the decision actually routes on.
|
|
343
|
+
*/
|
|
344
|
+
private chooseShape;
|
|
170
345
|
}
|
|
171
346
|
//#endregion
|
|
172
347
|
//#region src/summary-report.d.ts
|
|
@@ -460,5 +635,5 @@ interface ResearchReport {
|
|
|
460
635
|
*/
|
|
461
636
|
declare function researchReport(runs: RunRecord[], opts?: ResearchReportOptions): Promise<ResearchReport>;
|
|
462
637
|
//#endregion
|
|
463
|
-
export {
|
|
464
|
-
//# sourceMappingURL=summary-report-
|
|
638
|
+
export { PairedMcNemarEvidence as A, HeldOutGate as C, PairedDecisionMethod as D, SplitCoverage as E, PairedPromotionDecisionOptions as M, decidePairedPromotion as N, PairedDecisionShape as O, pairedDecisionShape as P, GateEvidence as S, HeldOutGateRejectionCode as T, paretoChart as _, ParetoPoint as a, DeltaStatistic as b, ResearchReportCandidate as c, ResearchReportOptions as d, ResearchReportRecommendation as f, gainHistogram as g, SummaryTableRow as h, ParetoFigureSpec as i, PairedPromotionDecision as j, PairedDecisionStatistic as k, ResearchReportDecision as l, SummaryTableOptions as m, GainDistributionFigureSpec as n, RESEARCH_REPORT_HARD_PAIR_FLOOR as o, SummaryTable as p, GainDistributionOptions as r, ResearchReport as s, GainDistributionBin as t, ResearchReportMethodology as u, researchReport as v, HeldOutGateConfig as w, GateDecision as x, summaryTable as y };
|
|
639
|
+
//# sourceMappingURL=summary-report-CPMINBqs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summary-report-CPMINBqs.d.ts","names":[],"sources":["../src/paired-promotion-decision.ts","../src/held-out-gate.ts","../src/summary-report.ts"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as NotFoundError } from "./errors-8YnH8WlF.js";
|
|
2
|
-
import { INPUT_VALUE, LLM_MODEL_ATTR_KEYS, OUTPUT_VALUE, SPAN_KIND_ATTR_KEYS, TOOL_ARGS_CAPTURED, TOOL_LATENCY_MS, TOOL_NAME, TOOL_NAME_ATTR_KEYS } from "./trace-attributes.js";
|
|
1
|
+
import { a as NotFoundError, n as CaptureIntegrityError } from "./errors-8YnH8WlF.js";
|
|
2
|
+
import { INPUT_VALUE, LLM_MODEL_ATTR_KEYS, OPENINFERENCE_SPAN_KIND, OUTPUT_VALUE, SPAN_KIND_ATTR_KEYS, TOOL_ARGS_CAPTURED, TOOL_LATENCY_MS, TOOL_NAME, TOOL_NAME_ATTR_KEYS } from "./trace-attributes.js";
|
|
3
3
|
import { AxJSRuntime, agent, f, fn } from "@ax-llm/ax";
|
|
4
4
|
import { readFile, stat } from "node:fs/promises";
|
|
5
5
|
//#region src/trace/otlp-attributes.ts
|
|
@@ -228,6 +228,41 @@ function compareSpanTime(a, b) {
|
|
|
228
228
|
return (spanEpochMillis(a) ?? 0) - (spanEpochMillis(b) ?? 0);
|
|
229
229
|
}
|
|
230
230
|
//#endregion
|
|
231
|
+
//#region src/trace/otlp-flat.ts
|
|
232
|
+
function createOtlpFlatLine(input) {
|
|
233
|
+
return {
|
|
234
|
+
trace_id: input.traceId,
|
|
235
|
+
span_id: input.spanId,
|
|
236
|
+
parent_span_id: input.parentSpanId,
|
|
237
|
+
name: input.name,
|
|
238
|
+
kind: input.kind,
|
|
239
|
+
start_time: input.startTime,
|
|
240
|
+
end_time: input.endTime,
|
|
241
|
+
status: {
|
|
242
|
+
code: input.statusCode,
|
|
243
|
+
...input.statusMessage !== void 0 ? { message: input.statusMessage } : {}
|
|
244
|
+
},
|
|
245
|
+
resource: input.resource,
|
|
246
|
+
attributes: input.attributes,
|
|
247
|
+
...input.events && input.events.length > 0 ? { events: input.events } : {}
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
/** Map the canonical trace status while letting each caller choose its legacy default. */
|
|
251
|
+
function spanStatusToOtlp(status, error, defaultCode) {
|
|
252
|
+
if (status === "error" || error) return "STATUS_CODE_ERROR";
|
|
253
|
+
if (status === "ok") return "STATUS_CODE_OK";
|
|
254
|
+
return defaultCode;
|
|
255
|
+
}
|
|
256
|
+
/** Convert epoch milliseconds, returning `undefined` for invalid or out-of-range values. */
|
|
257
|
+
function epochMillisToIso(value) {
|
|
258
|
+
if (!Number.isFinite(value)) return void 0;
|
|
259
|
+
try {
|
|
260
|
+
return new Date(value).toISOString();
|
|
261
|
+
} catch {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
//#endregion
|
|
231
266
|
//#region src/trace-analyst/store.ts
|
|
232
267
|
/** Compile a regex with the same anchoring + flags semantics across
|
|
233
268
|
* implementations. Throws on invalid pattern — callers should surface
|
|
@@ -299,25 +334,20 @@ const INDEX_YIELD_LINES = 5e3;
|
|
|
299
334
|
function yieldToEventLoop() {
|
|
300
335
|
return new Promise((resolve) => setImmediate(resolve));
|
|
301
336
|
}
|
|
302
|
-
var
|
|
303
|
-
path;
|
|
337
|
+
var BufferedOtlpTraceStore = class {
|
|
304
338
|
perAttributeViewBudget;
|
|
305
339
|
perAttributeSpanBudget;
|
|
306
340
|
perCallByteCeiling;
|
|
307
341
|
perMatchTextBudget;
|
|
308
|
-
maxFileBytes;
|
|
309
342
|
indexPromise;
|
|
310
|
-
/** Cached UTF-8 buffer
|
|
311
|
-
*
|
|
312
|
-
* syscall count. */
|
|
343
|
+
/** Cached UTF-8 buffer. Every read needs slice access, so each source is
|
|
344
|
+
* materialised once. */
|
|
313
345
|
bufferPromise;
|
|
314
346
|
constructor(opts) {
|
|
315
|
-
this.path = opts.path;
|
|
316
347
|
this.perAttributeViewBudget = opts.perAttributeViewBudget ?? DEFAULT_TRACE_ANALYST_BUDGETS.perAttributeViewBudget;
|
|
317
348
|
this.perAttributeSpanBudget = opts.perAttributeSpanBudget ?? DEFAULT_TRACE_ANALYST_BUDGETS.perAttributeSpanBudget;
|
|
318
349
|
this.perCallByteCeiling = opts.perCallByteCeiling ?? DEFAULT_TRACE_ANALYST_BUDGETS.perCallByteCeiling;
|
|
319
350
|
this.perMatchTextBudget = opts.perMatchTextBudget ?? DEFAULT_TRACE_ANALYST_BUDGETS.perMatchTextBudget;
|
|
320
|
-
this.maxFileBytes = opts.maxFileBytes ?? 268435456;
|
|
321
351
|
}
|
|
322
352
|
async getOverview(filters) {
|
|
323
353
|
const idx = await this.index();
|
|
@@ -497,23 +527,9 @@ var OtlpFileTraceStore = class {
|
|
|
497
527
|
await this.index();
|
|
498
528
|
}
|
|
499
529
|
async buffer() {
|
|
500
|
-
if (!this.bufferPromise) this.bufferPromise = this.
|
|
530
|
+
if (!this.bufferPromise) this.bufferPromise = this.readBuffer();
|
|
501
531
|
return this.bufferPromise;
|
|
502
532
|
}
|
|
503
|
-
/** Stat-then-read so an oversized file fails loud BEFORE we allocate a
|
|
504
|
-
* multi-hundred-MB Buffer and OOM the process. A missing file surfaces
|
|
505
|
-
* as TraceFileMissingError; any other stat/read error propagates. */
|
|
506
|
-
async readGuarded() {
|
|
507
|
-
let stats;
|
|
508
|
-
try {
|
|
509
|
-
stats = await stat(this.path);
|
|
510
|
-
} catch (err) {
|
|
511
|
-
if (err?.code === "ENOENT") throw new TraceFileMissingError(this.path);
|
|
512
|
-
throw err;
|
|
513
|
-
}
|
|
514
|
-
if (stats.size > this.maxFileBytes) throw new TraceFileTooLargeError(this.path, stats.size, this.maxFileBytes);
|
|
515
|
-
return readFile(this.path);
|
|
516
|
-
}
|
|
517
533
|
async index() {
|
|
518
534
|
if (!this.indexPromise) this.indexPromise = this.buildIndex();
|
|
519
535
|
return this.indexPromise;
|
|
@@ -752,6 +768,95 @@ var OtlpFileTraceStore = class {
|
|
|
752
768
|
};
|
|
753
769
|
}
|
|
754
770
|
};
|
|
771
|
+
var OtlpFileTraceStore = class extends BufferedOtlpTraceStore {
|
|
772
|
+
path;
|
|
773
|
+
maxFileBytes;
|
|
774
|
+
constructor(opts) {
|
|
775
|
+
super(opts);
|
|
776
|
+
this.path = opts.path;
|
|
777
|
+
this.maxFileBytes = opts.maxFileBytes ?? 268435456;
|
|
778
|
+
}
|
|
779
|
+
/** Stat-then-read so an oversized file fails loud before allocating the
|
|
780
|
+
* source buffer. Missing files remain distinct from malformed traces. */
|
|
781
|
+
async readBuffer() {
|
|
782
|
+
let stats;
|
|
783
|
+
try {
|
|
784
|
+
stats = await stat(this.path);
|
|
785
|
+
} catch (err) {
|
|
786
|
+
if (err?.code === "ENOENT") throw new TraceFileMissingError(this.path);
|
|
787
|
+
throw err;
|
|
788
|
+
}
|
|
789
|
+
if (stats.size > this.maxFileBytes) throw new TraceFileTooLargeError(this.path, stats.size, this.maxFileBytes);
|
|
790
|
+
return readFile(this.path);
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
var OtlpBufferTraceStore = class extends BufferedOtlpTraceStore {
|
|
794
|
+
source;
|
|
795
|
+
constructor(source, opts) {
|
|
796
|
+
super(opts);
|
|
797
|
+
this.source = source;
|
|
798
|
+
}
|
|
799
|
+
async readBuffer() {
|
|
800
|
+
return this.source;
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
/** Missing tool spans cannot distinguish a tool-free run from broken capture. */
|
|
804
|
+
var ToolTraceMissingError = class extends CaptureIntegrityError {
|
|
805
|
+
constructor() {
|
|
806
|
+
super("toolSpansToTraceAnalysisStore: no tool spans supplied; trace evidence is missing");
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
/**
|
|
810
|
+
* Snapshot canonical tool spans into the bounded read interface used by trace analysts.
|
|
811
|
+
* One `runId` becomes one trace; arguments, results, source attributes, errors, and timing
|
|
812
|
+
* remain queryable through the same OTLP projection as file-backed traces.
|
|
813
|
+
*/
|
|
814
|
+
function toolSpansToTraceAnalysisStore(spans, opts = {}) {
|
|
815
|
+
if (!spans || spans.length === 0) throw new ToolTraceMissingError();
|
|
816
|
+
const seen = /* @__PURE__ */ new Set();
|
|
817
|
+
const lines = spans.map((span, index) => {
|
|
818
|
+
assertToolSpanIdentity(span, index);
|
|
819
|
+
const identity = `${span.runId}\u0000${span.spanId}`;
|
|
820
|
+
if (seen.has(identity)) throw new CaptureIntegrityError(`toolSpansToTraceAnalysisStore: duplicate span '${span.spanId}' in run '${span.runId}'`);
|
|
821
|
+
seen.add(identity);
|
|
822
|
+
const attributes = { ...span.attributes ?? {} };
|
|
823
|
+
applyToolSpanOtlpAttributes(attributes, span);
|
|
824
|
+
attributes[OPENINFERENCE_SPAN_KIND] = "TOOL";
|
|
825
|
+
const endedAt = span.endedAt ?? span.startedAt + (span.latencyMs ?? 0);
|
|
826
|
+
const line = createOtlpFlatLine({
|
|
827
|
+
traceId: span.runId,
|
|
828
|
+
spanId: span.spanId,
|
|
829
|
+
parentSpanId: span.parentSpanId ?? null,
|
|
830
|
+
name: span.name,
|
|
831
|
+
kind: "SPAN_KIND_INTERNAL",
|
|
832
|
+
startTime: toolSpanTimeIso(span.startedAt, span.spanId, "startedAt"),
|
|
833
|
+
endTime: toolSpanTimeIso(endedAt, span.spanId, "endedAt"),
|
|
834
|
+
statusCode: spanStatusToOtlp(span.status, span.error, "STATUS_CODE_UNSET"),
|
|
835
|
+
statusMessage: span.error,
|
|
836
|
+
resource: { attributes: {} },
|
|
837
|
+
attributes
|
|
838
|
+
});
|
|
839
|
+
try {
|
|
840
|
+
return JSON.stringify(line);
|
|
841
|
+
} catch (cause) {
|
|
842
|
+
throw new CaptureIntegrityError(`toolSpansToTraceAnalysisStore: span '${span.spanId}' in run '${span.runId}' is not JSON-serializable`, { cause });
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
return new OtlpBufferTraceStore(Buffer.from(`${lines.join("\n")}\n`, "utf8"), opts);
|
|
846
|
+
}
|
|
847
|
+
function assertToolSpanIdentity(span, index) {
|
|
848
|
+
if (span.kind !== "tool") throw new CaptureIntegrityError(`toolSpansToTraceAnalysisStore: span at index ${index} has kind '${String(span.kind)}', not 'tool'`);
|
|
849
|
+
if (!span.runId || !span.spanId || !span.name || !span.toolName) throw new CaptureIntegrityError(`toolSpansToTraceAnalysisStore: span at index ${index} is missing runId, spanId, name, or toolName`);
|
|
850
|
+
if (!Number.isFinite(span.startedAt)) throw new CaptureIntegrityError(`toolSpansToTraceAnalysisStore: span '${span.spanId}' has invalid startedAt`);
|
|
851
|
+
if (span.endedAt !== void 0 && !Number.isFinite(span.endedAt)) throw new CaptureIntegrityError(`toolSpansToTraceAnalysisStore: span '${span.spanId}' has invalid endedAt`);
|
|
852
|
+
if (span.endedAt !== void 0 && span.endedAt < span.startedAt) throw new CaptureIntegrityError(`toolSpansToTraceAnalysisStore: span '${span.spanId}' ends before it starts`);
|
|
853
|
+
if (span.latencyMs !== void 0 && (!Number.isFinite(span.latencyMs) || span.latencyMs < 0)) throw new CaptureIntegrityError(`toolSpansToTraceAnalysisStore: span '${span.spanId}' has invalid latencyMs`);
|
|
854
|
+
}
|
|
855
|
+
function toolSpanTimeIso(value, spanId, field) {
|
|
856
|
+
const iso = epochMillisToIso(value);
|
|
857
|
+
if (iso) return iso;
|
|
858
|
+
throw new CaptureIntegrityError(`toolSpansToTraceAnalysisStore: span '${spanId}' has invalid ${field}`);
|
|
859
|
+
}
|
|
755
860
|
var TraceFileMissingError = class extends NotFoundError {
|
|
756
861
|
constructor(path) {
|
|
757
862
|
super(`trace file not found: ${path}`);
|
|
@@ -1080,6 +1185,6 @@ function assertStringArray(v, label) {
|
|
|
1080
1185
|
return v;
|
|
1081
1186
|
}
|
|
1082
1187
|
//#endregion
|
|
1083
|
-
export { traceSpanKindToOpenInferenceKind as
|
|
1188
|
+
export { stringField as C, traceSpanKindToOpenInferenceKind as D, isOtlpModelCall as E, spanEpochMillis as S, classifyOtlpSpanRole as T, extractOtlpAttributes as _, SpanNotFoundError as a, projectOtlpFlatLine as b, TraceNotFoundError as c, TRACE_ANALYST_TRUNCATION_MARKER_PREFIX as d, createOtlpFlatLine as f, compareSpanTime as g, asString as h, OtlpFileTraceStore as i, toolSpansToTraceAnalysisStore as l, spanStatusToOtlp as m, traceAnalystFunctionGroup as n, ToolTraceMissingError as o, epochMillisToIso as p, runTraceAnalysisLoop as r, TraceFileMissingError as s, buildTraceAnalystTools as t, DEFAULT_TRACE_ANALYST_BUDGETS as u, firstStringAttr as v, applyToolSpanOtlpAttributes as w, readOtlpStatus as x, inferOtlpKind as y };
|
|
1084
1189
|
|
|
1085
|
-
//# sourceMappingURL=tools-
|
|
1190
|
+
//# sourceMappingURL=tools-D8yTtNSN.js.map
|