@tangle-network/agent-eval 0.204.0 → 0.205.1
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 +8 -0
- package/dist/adapters/http.d.ts +1 -1
- package/dist/analyst/index.d.ts +1 -1
- package/dist/analyst/index.d.ts.map +1 -1
- package/dist/analyst/index.js +1 -1
- package/dist/{analyze-runs-BrIibNd0.js → analyze-runs-GZ0VSwui.js} +9 -9
- package/dist/{analyze-runs-BrIibNd0.js.map → analyze-runs-GZ0VSwui.js.map} +1 -1
- package/dist/baseline-Dm8966du.js +259 -0
- package/dist/baseline-Dm8966du.js.map +1 -0
- package/dist/{benchmark-command-Jv_puUGM.js → benchmark-command-DuDTN5ID.js} +6 -3
- package/dist/benchmark-command-DuDTN5ID.js.map +1 -0
- package/dist/benchmarks/index.d.ts +1 -1
- package/dist/benchmarks/index.js +2 -2
- package/dist/builder-eval/index.js +1 -1
- package/dist/campaign/index.d.ts +2 -2
- package/dist/campaign/index.js +9 -9
- package/dist/{campaign-DYhg4SjN.js → campaign-BL591dWw.js} +11 -11
- package/dist/{campaign-DYhg4SjN.js.map → campaign-BL591dWw.js.map} +1 -1
- package/dist/{campaign-evidence-Dt_rWPk2.js → campaign-evidence-DW3Krxov.js} +2 -2
- package/dist/{campaign-evidence-Dt_rWPk2.js.map → campaign-evidence-DW3Krxov.js.map} +1 -1
- package/dist/cli.js +5 -5
- package/dist/contract/index.d.ts +4 -4
- package/dist/contract/index.js +9 -9
- package/dist/{define-agent-eval-D8bPoBM5.d.ts → define-agent-eval-CKB6wyML.d.ts} +3 -3
- package/dist/{define-agent-eval-D8bPoBM5.d.ts.map → define-agent-eval-CKB6wyML.d.ts.map} +1 -1
- package/dist/{define-agent-eval-B25Cc3ki.js → define-agent-eval-CmItPfIq.js} +4 -4
- package/dist/{define-agent-eval-B25Cc3ki.js.map → define-agent-eval-CmItPfIq.js.map} +1 -1
- package/dist/{descriptive-D9LUFk1-.js → descriptive-CnU8Yq29.js} +2 -2
- package/dist/{descriptive-D9LUFk1-.js.map → descriptive-CnU8Yq29.js.map} +1 -1
- package/dist/diagnosis/index.js +1 -1
- package/dist/{eval-campaign-ha_aWtk7.js → eval-campaign-DU_p8qVt.js} +2 -2
- package/dist/{eval-campaign-ha_aWtk7.js.map → eval-campaign-DU_p8qVt.js.map} +1 -1
- package/dist/experiment/index.d.ts +1 -1
- package/dist/experiment/index.js +9 -9
- package/dist/{index-B8n12TR2.d.ts → index-BRdZ0oJo.d.ts} +2 -2
- package/dist/{index-B8n12TR2.d.ts.map → index-BRdZ0oJo.d.ts.map} +1 -1
- package/dist/{index-CG7mtu_G.d.ts → index-R8Xhod8e.d.ts} +4 -4
- package/dist/{index-CG7mtu_G.d.ts.map → index-R8Xhod8e.d.ts.map} +1 -1
- package/dist/index.d.ts +63 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +96 -19
- package/dist/index.js.map +1 -1
- package/dist/{internal-BOe3zmjQ.js → internal-CRHwS9iz.js} +3 -3
- package/dist/internal-CRHwS9iz.js.map +1 -0
- package/dist/jev.js +1 -1
- package/dist/judge-calibration-DFtEMlde.d.ts.map +1 -1
- package/dist/{judge-calibration-C5zgtiJT.js → judge-calibration-N44V04Se.js} +7 -17
- package/dist/judge-calibration-N44V04Se.js.map +1 -0
- package/dist/{llm-judge-DyZdrp6I.js → llm-judge-Fc3UIcPq.js} +8 -8
- package/dist/{llm-judge-DyZdrp6I.js.map → llm-judge-Fc3UIcPq.js.map} +1 -1
- package/dist/matrix/index.js +1 -1
- package/dist/{matrix-DnlHn5x1.js → matrix-_BV9-Lk5.js} +12 -9
- package/dist/matrix-_BV9-Lk5.js.map +1 -0
- package/dist/meta-eval/index.js +2 -2
- package/dist/multishot/golden/index.js +1 -1
- package/dist/multishot/index.js +1 -1
- package/dist/normal-DsJZtrJc.js +25 -0
- package/dist/normal-DsJZtrJc.js.map +1 -0
- package/dist/openapi.json +1 -1
- package/dist/{paired-arms-DpS_h8ON.js → paired-arms-CgWT7sHy.js} +6 -27
- package/dist/paired-arms-CgWT7sHy.js.map +1 -0
- package/dist/{paired-binary-OMBVRtc-.js → paired-binary-b2L3WKqr.js} +8 -5
- package/dist/paired-binary-b2L3WKqr.js.map +1 -0
- package/dist/{paired-promotion-decision-DhrmjdV8.js → paired-promotion-decision-2deJ7sDX.js} +3 -3
- package/dist/{paired-promotion-decision-DhrmjdV8.js.map → paired-promotion-decision-2deJ7sDX.js.map} +1 -1
- package/dist/{paired-tests-Ccr16gcW.js → paired-tests-jbwPdSz5.js} +3 -3
- package/dist/{paired-tests-Ccr16gcW.js.map → paired-tests-jbwPdSz5.js.map} +1 -1
- package/dist/pipelines/index.d.ts +1 -1
- package/dist/pipelines/index.js +2 -2
- package/dist/{power-and-mde-bie6i965.js → power-and-mde-fK5Qocwo.js} +3 -3
- package/dist/{power-and-mde-bie6i965.js.map → power-and-mde-fK5Qocwo.js.map} +1 -1
- package/dist/{produced-state-DLToUXrD.js → produced-state-CywjstcW.js} +3 -3
- package/dist/{produced-state-DLToUXrD.js.map → produced-state-CywjstcW.js.map} +1 -1
- package/dist/{promotion-policy-BI1RxzwD.js → promotion-policy-CCmyEH_c.js} +4 -4
- package/dist/{promotion-policy-BI1RxzwD.js.map → promotion-policy-CCmyEH_c.js.map} +1 -1
- package/dist/quantile--0yb07pD.js +15 -0
- package/dist/quantile--0yb07pD.js.map +1 -0
- package/dist/{report-command-D0XL2Cvi.js → report-command-JrjiWReS.js} +2 -2
- package/dist/{report-command-D0XL2Cvi.js.map → report-command-JrjiWReS.js.map} +1 -1
- package/dist/reporting.js +4 -4
- package/dist/{reward-hacking-C-a__ShZ.js → reward-hacking-BFcQgkux.js} +2 -2
- package/dist/{reward-hacking-C-a__ShZ.js.map → reward-hacking-BFcQgkux.js.map} +1 -1
- package/dist/rl.js +7 -7
- package/dist/{rubric-predictive-validity-B0Pg-3I5.js → rubric-predictive-validity-BsgkBfT0.js} +3 -3
- package/dist/{rubric-predictive-validity-B0Pg-3I5.js.map → rubric-predictive-validity-BsgkBfT0.js.map} +1 -1
- package/dist/search/index.js +2 -2
- package/dist/{search-kernel-Dpniloks.js → search-kernel-mzeNuzr8.js} +5 -5
- package/dist/{search-kernel-Dpniloks.js.map → search-kernel-mzeNuzr8.js.map} +1 -1
- package/dist/{search-summary-C1nAHgkB.js → search-summary-CDURlgZR.js} +4 -4
- package/dist/{search-summary-C1nAHgkB.js.map → search-summary-CDURlgZR.js.map} +1 -1
- package/dist/{sequential-DWn_np6P.js → sequential-DMqr3sni.js} +2 -2
- package/dist/{sequential-DWn_np6P.js.map → sequential-DMqr3sni.js.map} +1 -1
- package/dist/{skillopt-optimization-method-Dz5CGZb8.js → skillopt-optimization-method-BSgM_cbI.js} +4 -4
- package/dist/{skillopt-optimization-method-Dz5CGZb8.js.map → skillopt-optimization-method-BSgM_cbI.js.map} +1 -1
- package/dist/{statistical-heldout-CNLOvRq_.js → statistical-heldout-BP6iEJvR.js} +3 -3
- package/dist/{statistical-heldout-CNLOvRq_.js.map → statistical-heldout-BP6iEJvR.js.map} +1 -1
- package/dist/{statistical-heldout-Dq3pnmxd.d.ts → statistical-heldout-COfgpWah.d.ts} +3 -2
- package/dist/statistical-heldout-COfgpWah.d.ts.map +1 -0
- package/dist/{student-t-BBZYka0E.js → student-t-BQeKJx2N.js} +2 -2
- package/dist/{student-t-BBZYka0E.js.map → student-t-BQeKJx2N.js.map} +1 -1
- package/dist/{summary-report-DLzptLSA.js → summary-report-fXurE2TR.js} +6 -6
- package/dist/{summary-report-DLzptLSA.js.map → summary-report-fXurE2TR.js.map} +1 -1
- package/dist/supervisor-run/index.js +1 -1
- package/dist/{tool-waste-CP5DO383.d.ts → tool-waste-DxlebQkx.d.ts} +42 -2
- package/dist/tool-waste-DxlebQkx.d.ts.map +1 -0
- package/dist/{tool-waste-B-u3rzCz.js → tool-waste-x_E8h9Ih.js} +3 -3
- package/dist/{tool-waste-B-u3rzCz.js.map → tool-waste-x_E8h9Ih.js.map} +1 -1
- package/dist/trace-repair/index.js +1 -1
- package/dist/{tree-D48IJUK0.js → tree-By7LtLUu.js} +5 -5
- package/dist/{tree-D48IJUK0.js.map → tree-By7LtLUu.js.map} +1 -1
- package/docs/concepts.md +21 -0
- package/package.json +1 -1
- package/dist/baseline-CVKsfWTg.js +0 -150
- package/dist/baseline-CVKsfWTg.js.map +0 -1
- package/dist/benchmark-command-Jv_puUGM.js.map +0 -1
- package/dist/internal-BOe3zmjQ.js.map +0 -1
- package/dist/judge-calibration-C5zgtiJT.js.map +0 -1
- package/dist/matrix-DnlHn5x1.js.map +0 -1
- package/dist/paired-arms-DpS_h8ON.js.map +0 -1
- package/dist/paired-binary-OMBVRtc-.js.map +0 -1
- package/dist/statistical-heldout-Dq3pnmxd.d.ts.map +0 -1
- package/dist/tool-waste-CP5DO383.d.ts.map +0 -1
package/docs/concepts.md
CHANGED
|
@@ -414,3 +414,24 @@ release decision.
|
|
|
414
414
|
- **Reading a verdict someone else produced?** Read [verdicts.md](./verdicts.md) for what `certification` carries and what an absent one means.
|
|
415
415
|
- **Grading a finding by executing its repair?** Read [trace-repair-grader.md](./trace-repair-grader.md), and [trajectory-replay.md](./trajectory-replay.md) for re-executing a recorded failure.
|
|
416
416
|
- **Checking package ownership?** Read [charter.md](./charter.md) for the implemented foundations and host responsibilities.
|
|
417
|
+
|
|
418
|
+
## Independent sample statistics
|
|
419
|
+
|
|
420
|
+
The root export owns `summarizeSample`, `sampleMedian`, and `welchsTTest`.
|
|
421
|
+
`summarizeSample` reports finite sample facts, interpolated quartiles and a seeded median-bootstrap spread.
|
|
422
|
+
An empty sample returns `null`; a zero mean leaves relative dispersion (`cv`) unavailable.
|
|
423
|
+
Fewer than three observations retain a null `medianInterval`, not a confidence interval fabricated from the observed range.
|
|
424
|
+
The bootstrap interval describes the observed sample, not a significance test or generalization guarantee.
|
|
425
|
+
Confidence and resample count are explicit, and nonfinite input is refused.
|
|
426
|
+
Stability labels and decisions remain consumer policy.
|
|
427
|
+
|
|
428
|
+
`welchsTTest(a,b)` compares independent samples with positive direction meaning B minus A.
|
|
429
|
+
Its existing `status: 'ok'` result includes means, difference, standard error, t statistic, fractional degrees of freedom, two-sided p-value, effect size and 95% interval.
|
|
430
|
+
Too few samples or zero within-group variance retains the existing `insufficient-sample` or `zero-variance` status, observed means and difference, NaN inferential fields and a null interval.
|
|
431
|
+
An unavailable test is not evidence that the groups are equivalent.
|
|
432
|
+
Use the existing `cohensD` alongside it; an undefined standardized effect stays null.
|
|
433
|
+
|
|
434
|
+
The root also exports contingency-table inference, normal and Student-t tails, and centered linear-regression slope inference.
|
|
435
|
+
`twoByTwoPValue` chooses Fisher exact inference for sparse expected counts and the Pearson chi-squared tail otherwise.
|
|
436
|
+
A regression with insufficient residual degrees of freedom or constant x retains a null slope p-value.
|
|
437
|
+
These primitives do not choose product acceptance thresholds or authorize release.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-eval",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.205.1",
|
|
4
4
|
"description": "Evaluate and improve AI agents from runs, traces, judges, and feedback. Compare candidates, cluster failures, measure lift, and gate releases.",
|
|
5
5
|
"homepage": "https://github.com/tangle-network/agent-eval#readme",
|
|
6
6
|
"repository": {
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import { n as studentTQuantile, t as studentTCdf } from "./student-t-BBZYka0E.js";
|
|
2
|
-
import { n as cohensD } from "./effect-sizes-DiH8MGOH.js";
|
|
3
|
-
//#region src/baseline.ts
|
|
4
|
-
/**
|
|
5
|
-
* Baseline regression detection.
|
|
6
|
-
*
|
|
7
|
-
* Lifted from ADC baseline.ts. Every promotion-blocking signal boils down
|
|
8
|
-
* to: "is this run measurably worse than baseline?" — with enough
|
|
9
|
-
* statistical rigor to distinguish noise from drift.
|
|
10
|
-
*
|
|
11
|
-
* Uses:
|
|
12
|
-
* - Welch's t-test (unequal variance) for per-metric mean comparison
|
|
13
|
-
* - Cohen's d for effect size magnitude
|
|
14
|
-
* - IQR for stability flag (unstable samples can't be trusted for comparisons)
|
|
15
|
-
*
|
|
16
|
-
* Returns a structured verdict: improved | regressed | stable | unstable.
|
|
17
|
-
*/
|
|
18
|
-
/**
|
|
19
|
-
* Compare candidate samples against baseline per metric. Verdict logic:
|
|
20
|
-
* - unstable: IQR/|mean| > threshold on either set — not enough signal
|
|
21
|
-
* - improved: meaningful effect in the "better" direction AND p < alpha
|
|
22
|
-
* - regressed: meaningful effect in the "worse" direction AND p < alpha
|
|
23
|
-
* - stable: otherwise (no significant change)
|
|
24
|
-
*/
|
|
25
|
-
function compareToBaseline(samples, options = {}) {
|
|
26
|
-
const effectThreshold = options.effectThreshold ?? .5;
|
|
27
|
-
const alpha = options.alpha ?? .05;
|
|
28
|
-
const cvThreshold = options.unstableCvThreshold ?? .3;
|
|
29
|
-
const metrics = samples.map((s) => {
|
|
30
|
-
const comparison = welchsTTest(s.baseline, s.candidate);
|
|
31
|
-
if (comparison.status === "insufficient-sample") throw new Error(`compareToBaseline: need ≥2 samples per side for "${s.metric}"`);
|
|
32
|
-
const { meanA: bMean, meanB: cMean, delta, cohensD: d, t, df, p } = comparison;
|
|
33
|
-
const baselineIqr = iqr(s.baseline);
|
|
34
|
-
const candidateIqr = iqr(s.candidate);
|
|
35
|
-
const baselineStable = baselineIqr / Math.max(Math.abs(bMean), 1e-9) <= cvThreshold;
|
|
36
|
-
const candidateStable = candidateIqr / Math.max(Math.abs(cMean), 1e-9) <= cvThreshold;
|
|
37
|
-
const stable = baselineStable && candidateStable;
|
|
38
|
-
const reportedIqr = Math.max(baselineIqr, candidateIqr);
|
|
39
|
-
const effectClears = d === null || Math.abs(d) >= effectThreshold;
|
|
40
|
-
let verdict;
|
|
41
|
-
if (!stable) verdict = "unstable";
|
|
42
|
-
else if (comparison.status === "ok" && p < alpha && effectClears) verdict = (s.higherIsBetter ? delta > 0 : delta < 0) ? "improved" : "regressed";
|
|
43
|
-
else verdict = "stable";
|
|
44
|
-
return {
|
|
45
|
-
metric: s.metric,
|
|
46
|
-
baselineMean: bMean,
|
|
47
|
-
candidateMean: cMean,
|
|
48
|
-
delta,
|
|
49
|
-
cohensD: d,
|
|
50
|
-
welchT: t,
|
|
51
|
-
welchDf: df,
|
|
52
|
-
welchP: p,
|
|
53
|
-
stable,
|
|
54
|
-
iqr: reportedIqr,
|
|
55
|
-
verdict
|
|
56
|
-
};
|
|
57
|
-
});
|
|
58
|
-
return {
|
|
59
|
-
metrics,
|
|
60
|
-
hasRegression: metrics.some((m) => m.verdict === "regressed"),
|
|
61
|
-
hasUnstable: metrics.some((m) => m.verdict === "unstable")
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
function mean(xs) {
|
|
65
|
-
return xs.reduce((a, b) => a + b, 0) / xs.length;
|
|
66
|
-
}
|
|
67
|
-
/** Inter-quartile range; 0 when the sample has no spread. */
|
|
68
|
-
function iqr(xs) {
|
|
69
|
-
if (xs.length === 0) return 0;
|
|
70
|
-
const sorted = [...xs].sort((a, b) => a - b);
|
|
71
|
-
const q = (p) => {
|
|
72
|
-
const idx = p * (sorted.length - 1);
|
|
73
|
-
const lo = Math.floor(idx);
|
|
74
|
-
const hi = Math.ceil(idx);
|
|
75
|
-
return sorted[lo] + (sorted[hi] - sorted[lo]) * (idx - lo);
|
|
76
|
-
};
|
|
77
|
-
return q(.75) - q(.25);
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Welch's t-test and 95% interval for two independent samples.
|
|
81
|
-
*
|
|
82
|
-
* Uses the Student-t distribution at every finite degree of freedom. Fewer
|
|
83
|
-
* than two observations per side and zero observed standard error cannot
|
|
84
|
-
* define a t reference distribution; those cases return an explicit status,
|
|
85
|
-
* `NaN` inferential fields, and no interval rather than fabricated certainty.
|
|
86
|
-
*/
|
|
87
|
-
function welchsTTest(a, b) {
|
|
88
|
-
assertFiniteSample("a", a);
|
|
89
|
-
assertFiniteSample("b", b);
|
|
90
|
-
const mA = mean(a);
|
|
91
|
-
const mB = mean(b);
|
|
92
|
-
const delta = mB - mA;
|
|
93
|
-
if (a.length < 2 || b.length < 2) return {
|
|
94
|
-
status: "insufficient-sample",
|
|
95
|
-
meanA: mA,
|
|
96
|
-
meanB: mB,
|
|
97
|
-
delta,
|
|
98
|
-
standardError: NaN,
|
|
99
|
-
t: NaN,
|
|
100
|
-
df: NaN,
|
|
101
|
-
p: NaN,
|
|
102
|
-
ci95: null,
|
|
103
|
-
cohensD: null
|
|
104
|
-
};
|
|
105
|
-
const vA = variance(a, mA);
|
|
106
|
-
const vB = variance(b, mB);
|
|
107
|
-
const seSquared = vA / a.length + vB / b.length;
|
|
108
|
-
const d = cohensD(a, b);
|
|
109
|
-
if (seSquared === 0) return {
|
|
110
|
-
status: "zero-variance",
|
|
111
|
-
meanA: mA,
|
|
112
|
-
meanB: mB,
|
|
113
|
-
delta,
|
|
114
|
-
standardError: 0,
|
|
115
|
-
t: delta === 0 ? 0 : Math.sign(delta) * Number.POSITIVE_INFINITY,
|
|
116
|
-
df: NaN,
|
|
117
|
-
p: NaN,
|
|
118
|
-
ci95: null,
|
|
119
|
-
cohensD: null
|
|
120
|
-
};
|
|
121
|
-
const standardError = Math.sqrt(seSquared);
|
|
122
|
-
const t = delta / standardError;
|
|
123
|
-
const df = seSquared * seSquared / ((vA / a.length) ** 2 / (a.length - 1) + (vB / b.length) ** 2 / (b.length - 1));
|
|
124
|
-
const p = 2 * (1 - studentTCdf(Math.abs(t), df));
|
|
125
|
-
if (d === null) throw new Error("welchsTTest: non-zero standard error produced no pooled effect size");
|
|
126
|
-
const halfWidth = studentTQuantile(.975, df) * standardError;
|
|
127
|
-
return {
|
|
128
|
-
status: "ok",
|
|
129
|
-
meanA: mA,
|
|
130
|
-
meanB: mB,
|
|
131
|
-
delta,
|
|
132
|
-
standardError,
|
|
133
|
-
t,
|
|
134
|
-
df,
|
|
135
|
-
p,
|
|
136
|
-
ci95: [delta - halfWidth, delta + halfWidth],
|
|
137
|
-
cohensD: d
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
function variance(xs, m) {
|
|
141
|
-
return xs.reduce((acc, x) => acc + (x - m) ** 2, 0) / (xs.length - 1);
|
|
142
|
-
}
|
|
143
|
-
function assertFiniteSample(name, sample) {
|
|
144
|
-
const invalid = sample.find((value) => !Number.isFinite(value));
|
|
145
|
-
if (invalid !== void 0) throw new RangeError(`welchsTTest: sample ${name} must contain only finite values, got ${invalid}`);
|
|
146
|
-
}
|
|
147
|
-
//#endregion
|
|
148
|
-
export { iqr as n, welchsTTest as r, compareToBaseline as t };
|
|
149
|
-
|
|
150
|
-
//# sourceMappingURL=baseline-CVKsfWTg.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"baseline-CVKsfWTg.js","names":[],"sources":["../src/baseline.ts"],"sourcesContent":["/**\n * Baseline regression detection.\n *\n * Lifted from ADC baseline.ts. Every promotion-blocking signal boils down\n * to: \"is this run measurably worse than baseline?\" — with enough\n * statistical rigor to distinguish noise from drift.\n *\n * Uses:\n * - Welch's t-test (unequal variance) for per-metric mean comparison\n * - Cohen's d for effect size magnitude\n * - IQR for stability flag (unstable samples can't be trusted for comparisons)\n *\n * Returns a structured verdict: improved | regressed | stable | unstable.\n */\n\nimport { studentTCdf, studentTQuantile } from './math/student-t'\nimport { cohensD } from './statistics'\n\nexport interface MetricSamples {\n /** Stable metric key (e.g. \"overallScore\", \"firstTokenMs\"). */\n metric: string\n /** Whether higher values are better. */\n higherIsBetter: boolean\n baseline: number[]\n candidate: number[]\n}\n\nexport interface MetricVerdict {\n metric: string\n baselineMean: number\n candidateMean: number\n delta: number\n /** Null when both samples are constant with unequal means — the\n * standardized effect is unbounded there, not zero. */\n cohensD: number | null\n welchT: number\n welchDf: number\n welchP: number\n stable: boolean\n /** IQR of the combined samples — used as a rough stability indicator. */\n iqr: number\n verdict: 'improved' | 'regressed' | 'stable' | 'unstable'\n}\n\nexport interface BaselineReport {\n metrics: MetricVerdict[]\n /** True if any critical metric regressed. */\n hasRegression: boolean\n /** True if any metric is unstable (too noisy to judge). */\n hasUnstable: boolean\n}\n\nexport interface BaselineOptions {\n /** Effect size threshold for meaningful delta (default 0.5 — medium effect). */\n effectThreshold?: number\n /** p-value threshold for statistical significance (default 0.05). */\n alpha?: number\n /** IQR/mean ratio above which samples are flagged unstable (default 0.30). */\n unstableCvThreshold?: number\n}\n\nexport type WelchTestStatus = 'ok' | 'insufficient-sample' | 'zero-variance'\n\n/**\n * Full unequal-variance comparison for two independent samples.\n *\n * `meanB`, `delta`, `t`, and `cohensD` are oriented as `b - a`. Inferential\n * fields are `NaN` and `ci95` is null when the sample count or observed\n * variance cannot define a Student-t result. `status` makes that state\n * explicit so callers cannot mistake it for evidence of no difference.\n */\ninterface WelchTestResultBase {\n meanA: number\n meanB: number\n delta: number\n}\n\nexport type WelchTestResult =\n | (WelchTestResultBase & {\n status: 'ok'\n standardError: number\n t: number\n df: number\n p: number\n ci95: [number, number]\n cohensD: number\n })\n | (WelchTestResultBase & {\n status: 'insufficient-sample' | 'zero-variance'\n standardError: number\n t: number\n df: number\n p: number\n ci95: null\n cohensD: null\n })\n\n/**\n * Compare candidate samples against baseline per metric. Verdict logic:\n * - unstable: IQR/|mean| > threshold on either set — not enough signal\n * - improved: meaningful effect in the \"better\" direction AND p < alpha\n * - regressed: meaningful effect in the \"worse\" direction AND p < alpha\n * - stable: otherwise (no significant change)\n */\nexport function compareToBaseline(\n samples: MetricSamples[],\n options: BaselineOptions = {},\n): BaselineReport {\n const effectThreshold = options.effectThreshold ?? 0.5\n const alpha = options.alpha ?? 0.05\n const cvThreshold = options.unstableCvThreshold ?? 0.3\n\n const metrics: MetricVerdict[] = samples.map((s) => {\n const comparison = welchsTTest(s.baseline, s.candidate)\n if (comparison.status === 'insufficient-sample') {\n throw new Error(`compareToBaseline: need ≥2 samples per side for \"${s.metric}\"`)\n }\n const { meanA: bMean, meanB: cMean, delta, cohensD: d, t, df, p } = comparison\n // Stability is per-side: a comparison is trustworthy only when BOTH\n // samples are internally consistent. Combining the sides would flag\n // large-but-real deltas as \"unstable\" which is exactly what we want\n // to detect.\n const baselineIqr = iqr(s.baseline)\n const candidateIqr = iqr(s.candidate)\n const baselineStable = baselineIqr / Math.max(Math.abs(bMean), 1e-9) <= cvThreshold\n const candidateStable = candidateIqr / Math.max(Math.abs(cMean), 1e-9) <= cvThreshold\n const stable = baselineStable && candidateStable\n const reportedIqr = Math.max(baselineIqr, candidateIqr)\n\n // Both sides are guaranteed ≥ 2 samples above, so a null d means zero\n // pooled spread across a real mean gap: an unbounded standardized effect,\n // which clears any finite threshold.\n const effectClears = d === null || Math.abs(d) >= effectThreshold\n\n let verdict: MetricVerdict['verdict']\n if (!stable) {\n verdict = 'unstable'\n } else if (comparison.status === 'ok' && p < alpha && effectClears) {\n const candidateIsBetter = s.higherIsBetter ? delta > 0 : delta < 0\n verdict = candidateIsBetter ? 'improved' : 'regressed'\n } else {\n verdict = 'stable'\n }\n\n return {\n metric: s.metric,\n baselineMean: bMean,\n candidateMean: cMean,\n delta,\n cohensD: d,\n welchT: t,\n welchDf: df,\n welchP: p,\n stable,\n iqr: reportedIqr,\n verdict,\n }\n })\n\n return {\n metrics,\n hasRegression: metrics.some((m) => m.verdict === 'regressed'),\n hasUnstable: metrics.some((m) => m.verdict === 'unstable'),\n }\n}\n\nfunction mean(xs: number[]): number {\n return xs.reduce((a, b) => a + b, 0) / xs.length\n}\n\n/** Inter-quartile range; 0 when the sample has no spread. */\nexport function iqr(xs: number[]): number {\n if (xs.length === 0) return 0\n const sorted = [...xs].sort((a, b) => a - b)\n const q = (p: number) => {\n const idx = p * (sorted.length - 1)\n const lo = Math.floor(idx)\n const hi = Math.ceil(idx)\n return sorted[lo]! + (sorted[hi]! - sorted[lo]!) * (idx - lo)\n }\n return q(0.75) - q(0.25)\n}\n\n/**\n * Welch's t-test and 95% interval for two independent samples.\n *\n * Uses the Student-t distribution at every finite degree of freedom. Fewer\n * than two observations per side and zero observed standard error cannot\n * define a t reference distribution; those cases return an explicit status,\n * `NaN` inferential fields, and no interval rather than fabricated certainty.\n */\nexport function welchsTTest(a: number[], b: number[]): WelchTestResult {\n assertFiniteSample('a', a)\n assertFiniteSample('b', b)\n const mA = mean(a)\n const mB = mean(b)\n const delta = mB - mA\n if (a.length < 2 || b.length < 2) {\n return {\n status: 'insufficient-sample',\n meanA: mA,\n meanB: mB,\n delta,\n standardError: Number.NaN,\n t: Number.NaN,\n df: Number.NaN,\n p: Number.NaN,\n ci95: null,\n cohensD: null,\n }\n }\n\n const vA = variance(a, mA)\n const vB = variance(b, mB)\n const seSquared = vA / a.length + vB / b.length\n const d = cohensD(a, b)\n if (seSquared === 0) {\n return {\n status: 'zero-variance',\n meanA: mA,\n meanB: mB,\n delta,\n standardError: 0,\n t: delta === 0 ? 0 : Math.sign(delta) * Number.POSITIVE_INFINITY,\n df: Number.NaN,\n p: Number.NaN,\n ci95: null,\n cohensD: null,\n }\n }\n\n const standardError = Math.sqrt(seSquared)\n const t = delta / standardError\n const df =\n (seSquared * seSquared) /\n ((vA / a.length) ** 2 / (a.length - 1) + (vB / b.length) ** 2 / (b.length - 1))\n const p = 2 * (1 - studentTCdf(Math.abs(t), df))\n if (d === null) {\n throw new Error('welchsTTest: non-zero standard error produced no pooled effect size')\n }\n const halfWidth = studentTQuantile(0.975, df) * standardError\n return {\n status: 'ok',\n meanA: mA,\n meanB: mB,\n delta,\n standardError,\n t,\n df,\n p,\n ci95: [delta - halfWidth, delta + halfWidth],\n cohensD: d,\n }\n}\n\nfunction variance(xs: number[], m: number): number {\n return xs.reduce((acc, x) => acc + (x - m) ** 2, 0) / (xs.length - 1)\n}\n\nfunction assertFiniteSample(name: string, sample: readonly number[]): void {\n const invalid = sample.find((value) => !Number.isFinite(value))\n if (invalid !== undefined) {\n throw new RangeError(\n `welchsTTest: sample ${name} must contain only finite values, got ${invalid}`,\n )\n }\n}\n"],"mappings":"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"}
|