jevkit-calibrate 0.1.0

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/README.md ADDED
@@ -0,0 +1,93 @@
1
+ # jevkit-calibrate
2
+
3
+ Calibration is Jev's central claim: the probabilities are meant to track real
4
+ frequencies, so that among answers given 0.8, about 80% are right. TypeSafe
5
+ measures this across groups of predictions and says plainly that it does not
6
+ guarantee any individual answer.
7
+
8
+ This package checks the claim on **your** data, which is the part nobody else
9
+ can do for you, and turns the result into a threshold you can defend.
10
+
11
+ > Unofficial and unaffiliated with TypeSafe.
12
+
13
+ ```bash
14
+ pip install jevkit-calibrate
15
+ ```
16
+
17
+ Pure standard library. No numpy, no pandas, no plotting stack. A calibration
18
+ check that needs a build toolchain is a calibration check that does not get run.
19
+
20
+ ## Measure
21
+
22
+ ```python
23
+ from jevkit_core import read_records
24
+ from jevkit_calibrate import calibrate, observations_from_records
25
+
26
+ observations = observations_from_records(read_records("labeled.jevl"))
27
+ report = calibrate(observations)
28
+
29
+ print(report.summary())
30
+ print(report.diagram())
31
+ ```
32
+
33
+ ```
34
+ observations: 4000
35
+ accuracy: 0.7485
36
+ mean claimed: 0.7469 (underconfident by 0.0016)
37
+ ECE: 0.0094 (over 10 bins)
38
+ MCE: 0.0167
39
+ Brier: 0.1681
40
+ log loss: 0.5043
41
+ ```
42
+
43
+ `ECE` is the average gap between claimed and observed, weighted by how many
44
+ observations fall in each bin. `MCE` is the worst gap in any one bin, which is
45
+ what catches a healthy-looking ECE hiding one badly wrong region. `Brier` and
46
+ `log loss` are proper scoring rules: they reward being calibrated **and**
47
+ decisive, so a model that always says 0.5 scores badly even though it is
48
+ perfectly calibrated.
49
+
50
+ ## Pick a threshold
51
+
52
+ TypeSafe's confidence page recommends three bands and says where you draw them
53
+ depends on your data. This draws them from the data.
54
+
55
+ ```python
56
+ from jevkit_calibrate import recommend_for_accuracy
57
+
58
+ point = recommend_for_accuracy(observations, target_accuracy=0.95)
59
+ if point is None:
60
+ print("no threshold reaches 95% on this data")
61
+ else:
62
+ print(f"threshold {point.threshold:.2f}: "
63
+ f"covers {point.coverage:.1%} at {point.accuracy:.1%}, "
64
+ f"{point.errors} wrong answers acted on")
65
+ ```
66
+
67
+ `None` is a real answer, not a failure. It means this question cannot be
68
+ automated at that bar, and the honest move is to change the question rather
69
+ than lower the threshold.
70
+
71
+ ## CLI
72
+
73
+ ```bash
74
+ jevkit-calibrate labeled.jevl
75
+ jevkit-calibrate labeled.jevl --target-accuracy 0.95
76
+ jevkit-calibrate labeled.jevl --min-coverage 0.80
77
+ jevkit-calibrate labeled.jevl --max-ece 0.05 # CI gate
78
+ jevkit-calibrate labeled.jevl --format json
79
+ ```
80
+
81
+ ## Which quantity gets calibrated
82
+
83
+ By default, the probability mass on the chosen outcome, which is what "when it
84
+ says 0.8, is it right 80% of the time" means.
85
+
86
+ `--use-confidence` calibrates the API's `confidence` statistic instead. That is
87
+ a different question, and the one to ask when you want to know whether your
88
+ *routing* threshold sits in the right place. Nouls carry no confidence, so they
89
+ fall back to probability either way.
90
+
91
+ ## License
92
+
93
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ /** `jevkit-calibrate` command line interface. */
3
+ export declare const EXIT_OK = 0;
4
+ export declare const EXIT_FAILED_GATE = 1;
5
+ export declare const EXIT_USAGE = 2;
6
+ export declare function main(argv?: string[]): number;
7
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,iDAAiD;AAQjD,eAAO,MAAM,OAAO,IAAI,CAAC;AACzB,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAClC,eAAO,MAAM,UAAU,IAAI,CAAC;AAgB5B,wBAAgB,IAAI,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,MAAM,CAuGnE"}
package/dist/cli.js ADDED
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env node
2
+ /** `jevkit-calibrate` command line interface. */
3
+ import { RecordFormatError, readRecords } from "jevkit-core";
4
+ import { calibrate } from "./metrics.js";
5
+ import { observationsFromRecords } from "./records.js";
6
+ import { recommendForAccuracy, recommendForCoverage } from "./thresholds.js";
7
+ export const EXIT_OK = 0;
8
+ export const EXIT_FAILED_GATE = 1;
9
+ export const EXIT_USAGE = 2;
10
+ const USAGE = `usage: jevkit-calibrate <labeled.jevl...> [options]
11
+
12
+ Measure how well Jev's probabilities match outcomes on your labeled data, and
13
+ pick a confidence threshold from it. Never calls the API.
14
+
15
+ --bins N reliability bins (default 10)
16
+ --question ID restrict to this question id (repeatable)
17
+ --use-confidence calibrate the API's confidence, not the probability
18
+ --target-accuracy F recommend the lowest threshold reaching this accuracy
19
+ --min-coverage F recommend the highest threshold still covering this
20
+ --max-ece F exit non-zero if ECE exceeds this
21
+ --format text|json
22
+ --no-diagram`;
23
+ export function main(argv = process.argv.slice(2)) {
24
+ const files = [];
25
+ const questions = [];
26
+ let bins = 10;
27
+ let useConfidence = false;
28
+ let targetAccuracy = null;
29
+ let minCoverage = null;
30
+ let maxEce = null;
31
+ let format = "text";
32
+ let noDiagram = false;
33
+ for (let i = 0; i < argv.length; i++) {
34
+ const arg = argv[i];
35
+ switch (arg) {
36
+ case "--bins":
37
+ bins = Number(argv[++i]);
38
+ break;
39
+ case "--question":
40
+ questions.push(String(argv[++i]));
41
+ break;
42
+ case "--use-confidence":
43
+ useConfidence = true;
44
+ break;
45
+ case "--target-accuracy":
46
+ targetAccuracy = Number(argv[++i]);
47
+ break;
48
+ case "--min-coverage":
49
+ minCoverage = Number(argv[++i]);
50
+ break;
51
+ case "--max-ece":
52
+ maxEce = Number(argv[++i]);
53
+ break;
54
+ case "--no-diagram":
55
+ noDiagram = true;
56
+ break;
57
+ case "--format": {
58
+ const v = argv[++i];
59
+ if (v !== "text" && v !== "json") {
60
+ console.error("jevkit-calibrate: --format expects text or json");
61
+ return EXIT_USAGE;
62
+ }
63
+ format = v;
64
+ break;
65
+ }
66
+ case "-h":
67
+ case "--help":
68
+ console.log(USAGE);
69
+ return EXIT_OK;
70
+ default:
71
+ if (arg.startsWith("--")) {
72
+ console.error(`jevkit-calibrate: unknown option ${arg}`);
73
+ return EXIT_USAGE;
74
+ }
75
+ files.push(arg);
76
+ }
77
+ }
78
+ if (!files.length) {
79
+ console.error(USAGE);
80
+ return EXIT_USAGE;
81
+ }
82
+ const records = [];
83
+ try {
84
+ for (const path of files)
85
+ records.push(...readRecords(path));
86
+ }
87
+ catch (err) {
88
+ const message = err instanceof RecordFormatError ? err.message : err.message;
89
+ console.error(`jevkit-calibrate: ${message}`);
90
+ return EXIT_USAGE;
91
+ }
92
+ const observations = observationsFromRecords(records, {
93
+ questionIds: questions.length ? questions : undefined,
94
+ useConfidence,
95
+ });
96
+ if (!observations.length) {
97
+ console.error("jevkit-calibrate: no labeled observations found. Records need a 'label' object " +
98
+ "keyed by question id.");
99
+ return EXIT_USAGE;
100
+ }
101
+ const report = calibrate(observations, bins);
102
+ const recommendation = targetAccuracy !== null
103
+ ? { kind: "accuracy", target: targetAccuracy, point: recommendForAccuracy(observations, targetAccuracy) }
104
+ : minCoverage !== null
105
+ ? { kind: "coverage", target: minCoverage, point: recommendForCoverage(observations, minCoverage) }
106
+ : null;
107
+ if (format === "json") {
108
+ const payload = { ...report.toJSON() };
109
+ if (recommendation) {
110
+ const p = recommendation.point;
111
+ payload["recommendation"] = {
112
+ for: recommendation.kind, target: recommendation.target,
113
+ threshold: p?.threshold ?? null, coverage: p?.coverage ?? null,
114
+ accuracy: p?.accuracy ?? null, errors: p?.errors ?? null,
115
+ };
116
+ }
117
+ console.log(JSON.stringify(payload, null, 2));
118
+ }
119
+ else {
120
+ console.log(report.summary());
121
+ if (!noDiagram) {
122
+ console.log();
123
+ console.log(report.diagram());
124
+ }
125
+ if (recommendation) {
126
+ console.log();
127
+ const { kind, target, point } = recommendation;
128
+ if (!point) {
129
+ console.log(kind === "accuracy"
130
+ ? `No threshold reaches ${(target * 100).toFixed(1)}% accuracy on this data. The ` +
131
+ `question cannot be automated at that bar; change the question rather than the threshold.`
132
+ : `No threshold covers ${(target * 100).toFixed(1)}% of cases.`);
133
+ }
134
+ else {
135
+ console.log(`threshold ${point.threshold.toFixed(2)}: covers ${(point.coverage * 100).toFixed(1)}% ` +
136
+ `(${point.covered}/${point.total}) at ${(point.accuracy * 100).toFixed(1)}% accuracy, ` +
137
+ `${point.errors} wrong answer(s) acted on, ${point.escalated} escalated`);
138
+ }
139
+ }
140
+ }
141
+ if (maxEce !== null && report.ece > maxEce) {
142
+ console.error(`jevkit-calibrate: ECE ${report.ece.toFixed(4)} exceeds ${maxEce}`);
143
+ return EXIT_FAILED_GATE;
144
+ }
145
+ return EXIT_OK;
146
+ }
147
+ const invokedDirectly = process.argv[1] !== undefined && import.meta.url === `file://${process.argv[1]}`;
148
+ if (invokedDirectly)
149
+ process.exit(main());
150
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,iDAAiD;AAEjD,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAA6B,MAAM,aAAa,CAAC;AAExF,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAE7E,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,CAAC;AACzB,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAClC,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC;AAE5B,MAAM,KAAK,GAAG;;;;;;;;;;;;eAYC,CAAC;AAEhB,MAAM,UAAU,IAAI,CAAC,OAAiB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IACzD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,IAAI,aAAa,GAAG,KAAK,CAAC;IAC1B,IAAI,cAAc,GAAkB,IAAI,CAAC;IACzC,IAAI,WAAW,GAAkB,IAAI,CAAC;IACtC,IAAI,MAAM,GAAkB,IAAI,CAAC;IACjC,IAAI,MAAM,GAAoB,MAAM,CAAC;IACrC,IAAI,SAAS,GAAG,KAAK,CAAC;IAEtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;QACrB,QAAQ,GAAG,EAAE,CAAC;YACZ,KAAK,QAAQ;gBAAE,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;gBAAC,MAAM;YAC/C,KAAK,YAAY;gBAAE,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;gBAAC,MAAM;YAC5D,KAAK,kBAAkB;gBAAE,aAAa,GAAG,IAAI,CAAC;gBAAC,MAAM;YACrD,KAAK,mBAAmB;gBAAE,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;gBAAC,MAAM;YACpE,KAAK,gBAAgB;gBAAE,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;gBAAC,MAAM;YAC9D,KAAK,WAAW;gBAAE,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;gBAAC,MAAM;YACpD,KAAK,cAAc;gBAAE,SAAS,GAAG,IAAI,CAAC;gBAAC,MAAM;YAC7C,KAAK,UAAU,CAAC,CAAC,CAAC;gBAChB,MAAM,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;gBACpB,IAAI,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,MAAM,EAAE,CAAC;oBAAC,OAAO,CAAC,KAAK,CAAC,iDAAiD,CAAC,CAAC;oBAAC,OAAO,UAAU,CAAC;gBAAC,CAAC;gBAC1H,MAAM,GAAG,CAAC,CAAC;gBAAC,MAAM;YACpB,CAAC;YACD,KAAK,IAAI,CAAC;YAAC,KAAK,QAAQ;gBAAE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBAAC,OAAO,OAAO,CAAC;YAC7D;gBACE,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;oBAAC,OAAO,CAAC,KAAK,CAAC,oCAAoC,GAAG,EAAE,CAAC,CAAC;oBAAC,OAAO,UAAU,CAAC;gBAAC,CAAC;gBAC1G,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAAC,OAAO,UAAU,CAAC;IAAC,CAAC;IAE/D,MAAM,OAAO,GAAiB,EAAE,CAAC;IACjC,IAAI,CAAC;QACH,KAAK,MAAM,IAAI,IAAI,KAAK;YAAE,OAAO,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,iBAAiB,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAE,GAAa,CAAC,OAAO,CAAC;QACxF,OAAO,CAAC,KAAK,CAAC,qBAAqB,OAAO,EAAE,CAAC,CAAC;QAC9C,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,MAAM,YAAY,GAAG,uBAAuB,CAAC,OAAO,EAAE;QACpD,WAAW,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;QACrD,aAAa;KACd,CAAC,CAAC;IACH,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC;QACzB,OAAO,CAAC,KAAK,CACX,iFAAiF;YACjF,uBAAuB,CACxB,CAAC;QACF,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,MAAM,MAAM,GAAG,SAAS,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;IAC7C,MAAM,cAAc,GAClB,cAAc,KAAK,IAAI;QACrB,CAAC,CAAC,EAAE,IAAI,EAAE,UAAmB,EAAE,MAAM,EAAE,cAAc,EAAE,KAAK,EAAE,oBAAoB,CAAC,YAAY,EAAE,cAAc,CAAC,EAAE;QAClH,CAAC,CAAC,WAAW,KAAK,IAAI;YACpB,CAAC,CAAC,EAAE,IAAI,EAAE,UAAmB,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,oBAAoB,CAAC,YAAY,EAAE,WAAW,CAAC,EAAE;YAC5G,CAAC,CAAC,IAAI,CAAC;IAEb,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,MAAM,OAAO,GAA4B,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;QAChE,IAAI,cAAc,EAAE,CAAC;YACnB,MAAM,CAAC,GAAG,cAAc,CAAC,KAAK,CAAC;YAC/B,OAAO,CAAC,gBAAgB,CAAC,GAAG;gBAC1B,GAAG,EAAE,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,CAAC,MAAM;gBACvD,SAAS,EAAE,CAAC,EAAE,SAAS,IAAI,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,IAAI,IAAI;gBAC9D,QAAQ,EAAE,CAAC,EAAE,QAAQ,IAAI,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,IAAI,IAAI;aACzD,CAAC;QACJ,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAChD,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9B,IAAI,CAAC,SAAS,EAAE,CAAC;YAAC,OAAO,CAAC,GAAG,EAAE,CAAC;YAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;QAAC,CAAC;QACjE,IAAI,cAAc,EAAE,CAAC;YACnB,OAAO,CAAC,GAAG,EAAE,CAAC;YACd,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,cAAc,CAAC;YAC/C,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO,CAAC,GAAG,CACT,IAAI,KAAK,UAAU;oBACjB,CAAC,CAAC,wBAAwB,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,+BAA+B;wBAChF,0FAA0F;oBAC5F,CAAC,CAAC,uBAAuB,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,CAClE,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,GAAG,CACT,aAAa,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,QAAQ,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI;oBACxF,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,KAAK,QAAQ,CAAC,KAAK,CAAC,QAAQ,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc;oBACvF,GAAG,KAAK,CAAC,MAAM,8BAA8B,KAAK,CAAC,SAAS,YAAY,CACzE,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,GAAG,GAAG,MAAM,EAAE,CAAC;QAC3C,OAAO,CAAC,KAAK,CAAC,yBAAyB,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,MAAM,EAAE,CAAC,CAAC;QAClF,OAAO,gBAAgB,CAAC;IAC1B,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,eAAe,GACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,UAAU,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;AACnF,IAAI,eAAe;IAAE,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Verify Jev's calibration on your own data, and pick thresholds from it.
3
+ *
4
+ * Calibration is the central claim behind a System One model, and the one thing
5
+ * a user cannot check without tooling. This package computes reliability
6
+ * diagrams, ECE, MCE, Brier and log loss over labeled answers, and turns a
7
+ * labeled set into a defensible confidence threshold.
8
+ *
9
+ * No dependencies beyond jevkit-core. A calibration check that drags in a
10
+ * numerical stack is a calibration check that does not get run.
11
+ */
12
+ export { Bin, CalibrationReport, Observation, brierScore, calibrate, expectedCalibrationError, logLoss, maximumCalibrationError, reliabilityBins, type ObservationInit, } from "./metrics.js";
13
+ export { observationsFromRecords, type ExtractOptions } from "./records.js";
14
+ export { ThresholdPoint, recommendForAccuracy, recommendForCoverage, sweep, } from "./thresholds.js";
15
+ export declare const VERSION = "0.1.0";
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EACL,GAAG,EAAE,iBAAiB,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAC1D,wBAAwB,EAAE,OAAO,EAAE,uBAAuB,EAAE,eAAe,EAC3E,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,uBAAuB,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAC5E,OAAO,EACL,cAAc,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,KAAK,GAClE,MAAM,iBAAiB,CAAC;AAEzB,eAAO,MAAM,OAAO,UAAU,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Verify Jev's calibration on your own data, and pick thresholds from it.
3
+ *
4
+ * Calibration is the central claim behind a System One model, and the one thing
5
+ * a user cannot check without tooling. This package computes reliability
6
+ * diagrams, ECE, MCE, Brier and log loss over labeled answers, and turns a
7
+ * labeled set into a defensible confidence threshold.
8
+ *
9
+ * No dependencies beyond jevkit-core. A calibration check that drags in a
10
+ * numerical stack is a calibration check that does not get run.
11
+ */
12
+ export { Bin, CalibrationReport, Observation, brierScore, calibrate, expectedCalibrationError, logLoss, maximumCalibrationError, reliabilityBins, } from "./metrics.js";
13
+ export { observationsFromRecords } from "./records.js";
14
+ export { ThresholdPoint, recommendForAccuracy, recommendForCoverage, sweep, } from "./thresholds.js";
15
+ export const VERSION = "0.1.0";
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EACL,GAAG,EAAE,iBAAiB,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAC1D,wBAAwB,EAAE,OAAO,EAAE,uBAAuB,EAAE,eAAe,GAE5E,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,uBAAuB,EAAuB,MAAM,cAAc,CAAC;AAC5E,OAAO,EACL,cAAc,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,KAAK,GAClE,MAAM,iBAAiB,CAAC;AAEzB,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC"}
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Calibration metrics over labeled jev answers.
3
+ *
4
+ * Calibration is Jev's central claim: the probabilities are meant to reflect
5
+ * real-world frequencies, so that among answers given 0.8, about 80% are right.
6
+ * TypeSafe measures this across groups of predictions and says plainly that it
7
+ * does not guarantee any individual answer. This module is how you check the
8
+ * claim on *your* data, which is the part nobody else can do for you.
9
+ *
10
+ * Everything here is pure arithmetic over recorded answers. Nothing calls the
11
+ * API and there are no dependencies: a calibration check that drags in a
12
+ * numerical stack is a calibration check that does not get run.
13
+ */
14
+ export interface ObservationInit {
15
+ probability: number;
16
+ correct: boolean;
17
+ questionId?: string;
18
+ requestId?: string;
19
+ }
20
+ /**
21
+ * One labeled prediction.
22
+ *
23
+ * `probability` is the model's stated probability for the outcome it chose;
24
+ * `correct` is whether that outcome matched the label.
25
+ */
26
+ export declare class Observation {
27
+ readonly probability: number;
28
+ readonly correct: boolean;
29
+ readonly questionId: string;
30
+ readonly requestId: string;
31
+ constructor(init: ObservationInit);
32
+ }
33
+ /** One bucket of a reliability diagram. */
34
+ export declare class Bin {
35
+ readonly lower: number;
36
+ readonly upper: number;
37
+ readonly observations: Observation[];
38
+ constructor(lower: number, upper: number);
39
+ get count(): number;
40
+ /** What the model claimed, on average. */
41
+ get meanProbability(): number;
42
+ /** What actually happened. */
43
+ get accuracy(): number;
44
+ /** Claimed minus observed. Positive means overconfident. */
45
+ get gap(): number;
46
+ label(): string;
47
+ }
48
+ /**
49
+ * Bucket observations into equal-width probability bins.
50
+ *
51
+ * Equal-width rather than equal-count, because the question being asked is
52
+ * "when the model says 0.9, is it right 90% of the time", and that question is
53
+ * about fixed probability ranges. Empty bins are kept so the diagram does not
54
+ * silently mislead about coverage.
55
+ */
56
+ export declare function reliabilityBins(observations: Observation[], nBins?: number): Bin[];
57
+ /**
58
+ * ECE: average gap between claimed and observed, weighted by bin population.
59
+ *
60
+ * 0 is perfect. ECE depends on the bin count, so compare values only when they
61
+ * were computed with the same `nBins`.
62
+ */
63
+ export declare function expectedCalibrationError(observations: Observation[], nBins?: number): number;
64
+ /**
65
+ * MCE: the worst gap in any populated bin.
66
+ *
67
+ * ECE can look healthy while one region is badly wrong. MCE catches that, and
68
+ * it is the number that matters when one region is where your high-stakes
69
+ * decisions live.
70
+ */
71
+ export declare function maximumCalibrationError(observations: Observation[], nBins?: number): number;
72
+ /**
73
+ * Mean squared error between probability and outcome. Lower is better.
74
+ *
75
+ * Unlike ECE this is a proper scoring rule: it rewards being both calibrated
76
+ * and decisive, so a model that always says 0.5 scores poorly even though it is
77
+ * perfectly calibrated.
78
+ */
79
+ export declare function brierScore(observations: Observation[]): number;
80
+ /**
81
+ * Mean negative log likelihood. Lower is better.
82
+ *
83
+ * Punishes confident mistakes far harder than Brier does. `eps` clamps the
84
+ * probability away from 0 and 1, since an unclamped confident miss is infinite
85
+ * and would swamp every other observation.
86
+ */
87
+ export declare function logLoss(observations: Observation[], eps?: number): number;
88
+ export declare class CalibrationReport {
89
+ readonly observations: Observation[];
90
+ readonly nBins: number;
91
+ constructor(observations: Observation[], nBins?: number);
92
+ get count(): number;
93
+ get accuracy(): number;
94
+ get meanProbability(): number;
95
+ get bins(): Bin[];
96
+ get ece(): number;
97
+ get mce(): number;
98
+ get brier(): number;
99
+ get logLoss(): number;
100
+ /** Claimed probability exceeds observed accuracy overall. */
101
+ get overconfident(): boolean;
102
+ /**
103
+ * A text reliability diagram.
104
+ *
105
+ * Each row shows a bin's claimed probability against what actually happened,
106
+ * so a miscalibrated region is visible without plotting.
107
+ */
108
+ diagram(width?: number): string;
109
+ summary(): string;
110
+ toJSON(): {
111
+ count: number;
112
+ accuracy: number;
113
+ meanProbability: number;
114
+ overconfident: boolean;
115
+ ece: number;
116
+ mce: number;
117
+ brier: number;
118
+ logLoss: number;
119
+ nBins: number;
120
+ bins: {
121
+ lower: number;
122
+ upper: number;
123
+ count: number;
124
+ meanProbability: number;
125
+ accuracy: number;
126
+ gap: number;
127
+ }[];
128
+ };
129
+ }
130
+ export declare function calibrate(observations: Iterable<Observation>, nBins?: number): CalibrationReport;
131
+ //# sourceMappingURL=metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,qBAAa,WAAW;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,IAAI,EAAE,eAAe;CASlC;AAED,2CAA2C;AAC3C,qBAAa,GAAG;IAGF,QAAQ,CAAC,KAAK,EAAE,MAAM;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM;IAF1D,QAAQ,CAAC,YAAY,EAAE,WAAW,EAAE,CAAM;gBAErB,KAAK,EAAE,MAAM,EAAW,KAAK,EAAE,MAAM;IAE1D,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,0CAA0C;IAC1C,IAAI,eAAe,IAAI,MAAM,CAG5B;IAED,8BAA8B;IAC9B,IAAI,QAAQ,IAAI,MAAM,CAGrB;IAED,4DAA4D;IAC5D,IAAI,GAAG,IAAI,MAAM,CAEhB;IAED,KAAK,IAAI,MAAM;CAGhB;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,WAAW,EAAE,EAAE,KAAK,SAAK,GAAG,GAAG,EAAE,CAU9E;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,WAAW,EAAE,EAAE,KAAK,SAAK,GAAG,MAAM,CAMxF;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,WAAW,EAAE,EAAE,KAAK,SAAK,GAAG,MAAM,CAIvF;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,YAAY,EAAE,WAAW,EAAE,GAAG,MAAM,CAM9D;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,YAAY,EAAE,WAAW,EAAE,EAAE,GAAG,SAAQ,GAAG,MAAM,CAQxE;AAED,qBAAa,iBAAiB;IAChB,QAAQ,CAAC,YAAY,EAAE,WAAW,EAAE;IAAE,QAAQ,CAAC,KAAK;gBAA3C,YAAY,EAAE,WAAW,EAAE,EAAW,KAAK,SAAK;IAErE,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,IAAI,QAAQ,IAAI,MAAM,CAGrB;IAED,IAAI,eAAe,IAAI,MAAM,CAG5B;IAED,IAAI,IAAI,IAAI,GAAG,EAAE,CAEhB;IAED,IAAI,GAAG,IAAI,MAAM,CAEhB;IAED,IAAI,GAAG,IAAI,MAAM,CAEhB;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,6DAA6D;IAC7D,IAAI,aAAa,IAAI,OAAO,CAE3B;IAED;;;;;OAKG;IACH,OAAO,CAAC,KAAK,SAAK,GAAG,MAAM;IA6B3B,OAAO,IAAI,MAAM;IAejB,MAAM;;;;;;;;;;;;;;;;;;;CAiBP;AAED,wBAAgB,SAAS,CAAC,YAAY,EAAE,QAAQ,CAAC,WAAW,CAAC,EAAE,KAAK,SAAK,GAAG,iBAAiB,CAE5F"}
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Calibration metrics over labeled jev answers.
3
+ *
4
+ * Calibration is Jev's central claim: the probabilities are meant to reflect
5
+ * real-world frequencies, so that among answers given 0.8, about 80% are right.
6
+ * TypeSafe measures this across groups of predictions and says plainly that it
7
+ * does not guarantee any individual answer. This module is how you check the
8
+ * claim on *your* data, which is the part nobody else can do for you.
9
+ *
10
+ * Everything here is pure arithmetic over recorded answers. Nothing calls the
11
+ * API and there are no dependencies: a calibration check that drags in a
12
+ * numerical stack is a calibration check that does not get run.
13
+ */
14
+ /**
15
+ * One labeled prediction.
16
+ *
17
+ * `probability` is the model's stated probability for the outcome it chose;
18
+ * `correct` is whether that outcome matched the label.
19
+ */
20
+ export class Observation {
21
+ probability;
22
+ correct;
23
+ questionId;
24
+ requestId;
25
+ constructor(init) {
26
+ if (!(init.probability >= 0 && init.probability <= 1)) {
27
+ throw new RangeError(`probability must be in [0, 1], got ${init.probability}`);
28
+ }
29
+ this.probability = init.probability;
30
+ this.correct = init.correct;
31
+ this.questionId = init.questionId ?? "";
32
+ this.requestId = init.requestId ?? "";
33
+ }
34
+ }
35
+ /** One bucket of a reliability diagram. */
36
+ export class Bin {
37
+ lower;
38
+ upper;
39
+ observations = [];
40
+ constructor(lower, upper) {
41
+ this.lower = lower;
42
+ this.upper = upper;
43
+ }
44
+ get count() {
45
+ return this.observations.length;
46
+ }
47
+ /** What the model claimed, on average. */
48
+ get meanProbability() {
49
+ if (!this.count)
50
+ return 0;
51
+ return this.observations.reduce((a, o) => a + o.probability, 0) / this.count;
52
+ }
53
+ /** What actually happened. */
54
+ get accuracy() {
55
+ if (!this.count)
56
+ return 0;
57
+ return this.observations.filter((o) => o.correct).length / this.count;
58
+ }
59
+ /** Claimed minus observed. Positive means overconfident. */
60
+ get gap() {
61
+ return this.meanProbability - this.accuracy;
62
+ }
63
+ label() {
64
+ return `[${this.lower.toFixed(2)}, ${this.upper.toFixed(2)}${this.upper === 1 ? "]" : ")"}`;
65
+ }
66
+ }
67
+ /**
68
+ * Bucket observations into equal-width probability bins.
69
+ *
70
+ * Equal-width rather than equal-count, because the question being asked is
71
+ * "when the model says 0.9, is it right 90% of the time", and that question is
72
+ * about fixed probability ranges. Empty bins are kept so the diagram does not
73
+ * silently mislead about coverage.
74
+ */
75
+ export function reliabilityBins(observations, nBins = 10) {
76
+ if (nBins < 1)
77
+ throw new RangeError("nBins must be at least 1");
78
+ const bins = [];
79
+ for (let i = 0; i < nBins; i++)
80
+ bins.push(new Bin(i / nBins, (i + 1) / nBins));
81
+ for (const obs of observations) {
82
+ // The top bin is closed so a probability of exactly 1 lands in it.
83
+ const index = Math.min(Math.floor(obs.probability * nBins), nBins - 1);
84
+ bins[index].observations.push(obs);
85
+ }
86
+ return bins;
87
+ }
88
+ /**
89
+ * ECE: average gap between claimed and observed, weighted by bin population.
90
+ *
91
+ * 0 is perfect. ECE depends on the bin count, so compare values only when they
92
+ * were computed with the same `nBins`.
93
+ */
94
+ export function expectedCalibrationError(observations, nBins = 10) {
95
+ if (!observations.length)
96
+ return 0;
97
+ const total = observations.length;
98
+ return reliabilityBins(observations, nBins)
99
+ .filter((b) => b.count)
100
+ .reduce((sum, b) => sum + (b.count / total) * Math.abs(b.gap), 0);
101
+ }
102
+ /**
103
+ * MCE: the worst gap in any populated bin.
104
+ *
105
+ * ECE can look healthy while one region is badly wrong. MCE catches that, and
106
+ * it is the number that matters when one region is where your high-stakes
107
+ * decisions live.
108
+ */
109
+ export function maximumCalibrationError(observations, nBins = 10) {
110
+ if (!observations.length)
111
+ return 0;
112
+ const populated = reliabilityBins(observations, nBins).filter((b) => b.count);
113
+ return populated.reduce((m, b) => Math.max(m, Math.abs(b.gap)), 0);
114
+ }
115
+ /**
116
+ * Mean squared error between probability and outcome. Lower is better.
117
+ *
118
+ * Unlike ECE this is a proper scoring rule: it rewards being both calibrated
119
+ * and decisive, so a model that always says 0.5 scores poorly even though it is
120
+ * perfectly calibrated.
121
+ */
122
+ export function brierScore(observations) {
123
+ if (!observations.length)
124
+ return 0;
125
+ return (observations.reduce((sum, o) => sum + (o.probability - (o.correct ? 1 : 0)) ** 2, 0) /
126
+ observations.length);
127
+ }
128
+ /**
129
+ * Mean negative log likelihood. Lower is better.
130
+ *
131
+ * Punishes confident mistakes far harder than Brier does. `eps` clamps the
132
+ * probability away from 0 and 1, since an unclamped confident miss is infinite
133
+ * and would swamp every other observation.
134
+ */
135
+ export function logLoss(observations, eps = 1e-15) {
136
+ if (!observations.length)
137
+ return 0;
138
+ let total = 0;
139
+ for (const o of observations) {
140
+ const p = Math.min(Math.max(o.probability, eps), 1 - eps);
141
+ total -= o.correct ? Math.log(p) : Math.log(1 - p);
142
+ }
143
+ return total / observations.length;
144
+ }
145
+ export class CalibrationReport {
146
+ observations;
147
+ nBins;
148
+ constructor(observations, nBins = 10) {
149
+ this.observations = observations;
150
+ this.nBins = nBins;
151
+ }
152
+ get count() {
153
+ return this.observations.length;
154
+ }
155
+ get accuracy() {
156
+ if (!this.count)
157
+ return 0;
158
+ return this.observations.filter((o) => o.correct).length / this.count;
159
+ }
160
+ get meanProbability() {
161
+ if (!this.count)
162
+ return 0;
163
+ return this.observations.reduce((a, o) => a + o.probability, 0) / this.count;
164
+ }
165
+ get bins() {
166
+ return reliabilityBins(this.observations, this.nBins);
167
+ }
168
+ get ece() {
169
+ return expectedCalibrationError(this.observations, this.nBins);
170
+ }
171
+ get mce() {
172
+ return maximumCalibrationError(this.observations, this.nBins);
173
+ }
174
+ get brier() {
175
+ return brierScore(this.observations);
176
+ }
177
+ get logLoss() {
178
+ return logLoss(this.observations);
179
+ }
180
+ /** Claimed probability exceeds observed accuracy overall. */
181
+ get overconfident() {
182
+ return this.meanProbability > this.accuracy;
183
+ }
184
+ /**
185
+ * A text reliability diagram.
186
+ *
187
+ * Each row shows a bin's claimed probability against what actually happened,
188
+ * so a miscalibrated region is visible without plotting.
189
+ */
190
+ diagram(width = 28) {
191
+ const lines = [
192
+ `${"bin".padEnd(14)} ${"n".padStart(5)} ${"claimed".padStart(8)} ` +
193
+ `${"actual".padStart(8)} ${"gap".padStart(7)}`,
194
+ "-".repeat(46),
195
+ ];
196
+ for (const b of this.bins) {
197
+ if (!b.count) {
198
+ lines.push(`${b.label().padEnd(14)} ${String(0).padStart(5)} ${"-".padStart(8)} ` +
199
+ `${"-".padStart(8)} ${"-".padStart(7)}`);
200
+ continue;
201
+ }
202
+ const barLen = Math.floor(b.accuracy * width);
203
+ const marker = Math.floor(b.meanProbability * width);
204
+ const cells = Array.from({ length: width }, (_, i) => (i < barLen ? "#" : " "));
205
+ if (marker >= 0 && marker < width)
206
+ cells[marker] = cells[marker] === " " ? "|" : "+";
207
+ const gap = `${b.gap >= 0 ? "+" : ""}${b.gap.toFixed(3)}`;
208
+ lines.push(`${b.label().padEnd(14)} ${String(b.count).padStart(5)} ` +
209
+ `${b.meanProbability.toFixed(3).padStart(8)} ${b.accuracy.toFixed(3).padStart(8)} ` +
210
+ `${gap.padStart(7)} ${cells.join("")}`);
211
+ }
212
+ lines.push("", " # observed accuracy, | claimed probability, + both");
213
+ return lines.join("\n");
214
+ }
215
+ summary() {
216
+ if (!this.count)
217
+ return "no labeled observations";
218
+ const direction = this.overconfident ? "overconfident" : "underconfident";
219
+ const gap = Math.abs(this.meanProbability - this.accuracy);
220
+ return [
221
+ `observations: ${this.count}`,
222
+ `accuracy: ${this.accuracy.toFixed(4)}`,
223
+ `mean claimed: ${this.meanProbability.toFixed(4)} (${direction} by ${gap.toFixed(4)})`,
224
+ `ECE: ${this.ece.toFixed(4)} (over ${this.nBins} bins)`,
225
+ `MCE: ${this.mce.toFixed(4)}`,
226
+ `Brier: ${this.brier.toFixed(4)}`,
227
+ `log loss: ${this.logLoss.toFixed(4)}`,
228
+ ].join("\n");
229
+ }
230
+ toJSON() {
231
+ return {
232
+ count: this.count,
233
+ accuracy: this.accuracy,
234
+ meanProbability: this.meanProbability,
235
+ overconfident: this.overconfident,
236
+ ece: this.ece,
237
+ mce: this.mce,
238
+ brier: this.brier,
239
+ logLoss: this.logLoss,
240
+ nBins: this.nBins,
241
+ bins: this.bins.map((b) => ({
242
+ lower: b.lower, upper: b.upper, count: b.count,
243
+ meanProbability: b.meanProbability, accuracy: b.accuracy, gap: b.gap,
244
+ })),
245
+ };
246
+ }
247
+ }
248
+ export function calibrate(observations, nBins = 10) {
249
+ return new CalibrationReport([...observations], nBins);
250
+ }
251
+ //# sourceMappingURL=metrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.js","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /** Turning labeled `.jevl` records into calibration observations. */
2
+ import { type Record as JevlRecord } from "jevkit-core";
3
+ import { Observation } from "./metrics.js";
4
+ export interface ExtractOptions {
5
+ questionIds?: Iterable<string>;
6
+ /**
7
+ * Which quantity to calibrate.
8
+ *
9
+ * The default, `false`, uses the probability mass on the chosen outcome,
10
+ * which is what "when it says 0.8, is it right 80% of the time" means.
11
+ * `true` calibrates the API's `confidence` statistic instead, which is a
12
+ * different question and answers whether your *routing* threshold is well
13
+ * placed. Nouls have no confidence, so they fall back to probability either
14
+ * way.
15
+ */
16
+ useConfidence?: boolean;
17
+ }
18
+ /**
19
+ * Extract one observation per labeled answer.
20
+ *
21
+ * Records with no `label` are skipped: calibration needs ground truth, and
22
+ * silently treating an unlabeled record as correct or incorrect would poison
23
+ * every number downstream.
24
+ */
25
+ export declare function observationsFromRecords(records: Iterable<JevlRecord>, options?: ExtractOptions): Observation[];
26
+ //# sourceMappingURL=records.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"records.d.ts","sourceRoot":"","sources":["../src/records.ts"],"names":[],"mappings":"AAAA,qEAAqE;AAErE,OAAO,EAAE,KAAK,MAAM,IAAI,UAAU,EAAgB,MAAM,aAAa,CAAC;AAEtE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,MAAM,WAAW,cAAc;IAC7B,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC/B;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,QAAQ,CAAC,UAAU,CAAC,EAC7B,OAAO,GAAE,cAAmB,GAC3B,WAAW,EAAE,CA0Bf"}
@@ -0,0 +1,37 @@
1
+ /** Turning labeled `.jevl` records into calibration observations. */
2
+ import { parseAnswers } from "jevkit-core";
3
+ import { Observation } from "./metrics.js";
4
+ /**
5
+ * Extract one observation per labeled answer.
6
+ *
7
+ * Records with no `label` are skipped: calibration needs ground truth, and
8
+ * silently treating an unlabeled record as correct or incorrect would poison
9
+ * every number downstream.
10
+ */
11
+ export function observationsFromRecords(records, options = {}) {
12
+ const wanted = options.questionIds ? new Set(options.questionIds) : null;
13
+ const out = [];
14
+ for (const record of records) {
15
+ if (!record.label || !Object.keys(record.label).length)
16
+ continue;
17
+ const answers = parseAnswers(record.answers);
18
+ for (const [qid, label] of Object.entries(record.label)) {
19
+ if (wanted && !wanted.has(qid))
20
+ continue;
21
+ const answer = answers[qid];
22
+ if (!answer)
23
+ continue;
24
+ const probability = options.useConfidence && answer.confidence !== null
25
+ ? answer.confidence
26
+ : answer.topProbability;
27
+ out.push(new Observation({
28
+ probability,
29
+ correct: answer.isCorrect(label),
30
+ questionId: qid,
31
+ requestId: record.requestId,
32
+ }));
33
+ }
34
+ }
35
+ return out;
36
+ }
37
+ //# sourceMappingURL=records.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"records.js","sourceRoot":"","sources":["../src/records.ts"],"names":[],"mappings":"AAAA,qEAAqE;AAErE,OAAO,EAA6B,YAAY,EAAE,MAAM,aAAa,CAAC;AAEtE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAiB3C;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CACrC,OAA6B,EAC7B,UAA0B,EAAE;IAE5B,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACzE,MAAM,GAAG,GAAkB,EAAE,CAAC;IAE9B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM;YAAE,SAAS;QACjE,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC7C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YACxD,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,SAAS;YACzC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;YAC5B,IAAI,CAAC,MAAM;gBAAE,SAAS;YACtB,MAAM,WAAW,GACf,OAAO,CAAC,aAAa,IAAI,MAAM,CAAC,UAAU,KAAK,IAAI;gBACjD,CAAC,CAAC,MAAM,CAAC,UAAU;gBACnB,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC;YAC5B,GAAG,CAAC,IAAI,CACN,IAAI,WAAW,CAAC;gBACd,WAAW;gBACX,OAAO,EAAE,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC;gBAChC,UAAU,EAAE,GAAG;gBACf,SAAS,EAAE,MAAM,CAAC,SAAS;aAC5B,CAAC,CACH,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Choosing a confidence threshold from labeled data instead of guessing.
3
+ *
4
+ * TypeSafe's confidence page recommends three bands: act automatically, proceed
5
+ * with caution, escalate. It also says plainly that where you draw those lines
6
+ * depends on your domain and your data. This module draws them from the data.
7
+ *
8
+ * The trade is always the same. Raising the threshold means acting on fewer
9
+ * cases but being right more often on the ones you do act on. That is a curve,
10
+ * not a number, so `sweep` returns the whole curve and the `recommendFor*`
11
+ * functions pick a point on it against a constraint you state.
12
+ */
13
+ import type { Observation } from "./metrics.js";
14
+ /** What happens if you auto-handle everything at or above `threshold`. */
15
+ export declare class ThresholdPoint {
16
+ readonly threshold: number;
17
+ readonly covered: number;
18
+ readonly total: number;
19
+ readonly correct: number;
20
+ constructor(threshold: number, covered: number, total: number, correct: number);
21
+ /** Fraction of cases handled automatically. */
22
+ get coverage(): number;
23
+ /**
24
+ * Accuracy on the automatically handled cases.
25
+ *
26
+ * Defined as 1 when nothing is covered: a threshold that acts on nothing is
27
+ * never wrong. Read it together with `coverage`, never alone.
28
+ */
29
+ get accuracy(): number;
30
+ get escalated(): number;
31
+ /** Wrong answers you acted on. Usually the number that actually costs. */
32
+ get errors(): number;
33
+ }
34
+ /**
35
+ * Coverage and accuracy at every threshold from 0 to 1.
36
+ *
37
+ * Uses the probability the model put on the outcome it chose, so this works for
38
+ * Choice and Score confidence and for a Noul's distance from 0.5 alike, as long
39
+ * as the caller is consistent about which it fed in.
40
+ */
41
+ export declare function sweep(observations: Observation[], steps?: number): ThresholdPoint[];
42
+ /**
43
+ * Lowest threshold reaching `targetAccuracy`, so coverage stays highest.
44
+ *
45
+ * Returns `null` when no threshold reaches the target, which is a real answer:
46
+ * it means this question cannot be automated at that accuracy and the honest
47
+ * move is to change the question rather than the threshold.
48
+ */
49
+ export declare function recommendForAccuracy(observations: Observation[], targetAccuracy: number, steps?: number): ThresholdPoint | null;
50
+ /**
51
+ * Highest threshold still covering `minCoverage`, so accuracy is best.
52
+ *
53
+ * The mirror of `recommendForAccuracy`: use it when throughput is the binding
54
+ * constraint and you want the most accurate threshold that still keeps enough
55
+ * volume out of the review queue.
56
+ */
57
+ export declare function recommendForCoverage(observations: Observation[], minCoverage: number, steps?: number): ThresholdPoint | null;
58
+ //# sourceMappingURL=thresholds.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thresholds.d.ts","sourceRoot":"","sources":["../src/thresholds.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,0EAA0E;AAC1E,qBAAa,cAAc;IAEvB,QAAQ,CAAC,SAAS,EAAE,MAAM;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM;gBAHf,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM;IAG1B,+CAA+C;IAC/C,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED;;;;;OAKG;IACH,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,0EAA0E;IAC1E,IAAI,MAAM,IAAI,MAAM,CAEnB;CACF;AAED;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,YAAY,EAAE,WAAW,EAAE,EAAE,KAAK,SAAM,GAAG,cAAc,EAAE,CAiBhF;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,WAAW,EAAE,EAC3B,cAAc,EAAE,MAAM,EACtB,KAAK,SAAM,GACV,cAAc,GAAG,IAAI,CASvB;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,WAAW,EAAE,EAC3B,WAAW,EAAE,MAAM,EACnB,KAAK,SAAM,GACV,cAAc,GAAG,IAAI,CAOvB"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Choosing a confidence threshold from labeled data instead of guessing.
3
+ *
4
+ * TypeSafe's confidence page recommends three bands: act automatically, proceed
5
+ * with caution, escalate. It also says plainly that where you draw those lines
6
+ * depends on your domain and your data. This module draws them from the data.
7
+ *
8
+ * The trade is always the same. Raising the threshold means acting on fewer
9
+ * cases but being right more often on the ones you do act on. That is a curve,
10
+ * not a number, so `sweep` returns the whole curve and the `recommendFor*`
11
+ * functions pick a point on it against a constraint you state.
12
+ */
13
+ /** What happens if you auto-handle everything at or above `threshold`. */
14
+ export class ThresholdPoint {
15
+ threshold;
16
+ covered;
17
+ total;
18
+ correct;
19
+ constructor(threshold, covered, total, correct) {
20
+ this.threshold = threshold;
21
+ this.covered = covered;
22
+ this.total = total;
23
+ this.correct = correct;
24
+ }
25
+ /** Fraction of cases handled automatically. */
26
+ get coverage() {
27
+ return this.total ? this.covered / this.total : 0;
28
+ }
29
+ /**
30
+ * Accuracy on the automatically handled cases.
31
+ *
32
+ * Defined as 1 when nothing is covered: a threshold that acts on nothing is
33
+ * never wrong. Read it together with `coverage`, never alone.
34
+ */
35
+ get accuracy() {
36
+ return this.covered ? this.correct / this.covered : 1;
37
+ }
38
+ get escalated() {
39
+ return this.total - this.covered;
40
+ }
41
+ /** Wrong answers you acted on. Usually the number that actually costs. */
42
+ get errors() {
43
+ return this.covered - this.correct;
44
+ }
45
+ }
46
+ /**
47
+ * Coverage and accuracy at every threshold from 0 to 1.
48
+ *
49
+ * Uses the probability the model put on the outcome it chose, so this works for
50
+ * Choice and Score confidence and for a Noul's distance from 0.5 alike, as long
51
+ * as the caller is consistent about which it fed in.
52
+ */
53
+ export function sweep(observations, steps = 101) {
54
+ if (steps < 2)
55
+ throw new RangeError("steps must be at least 2");
56
+ const total = observations.length;
57
+ const points = [];
58
+ for (let i = 0; i < steps; i++) {
59
+ const threshold = i / (steps - 1);
60
+ const covered = observations.filter((o) => o.probability >= threshold);
61
+ points.push(new ThresholdPoint(threshold, covered.length, total, covered.filter((o) => o.correct).length));
62
+ }
63
+ return points;
64
+ }
65
+ /**
66
+ * Lowest threshold reaching `targetAccuracy`, so coverage stays highest.
67
+ *
68
+ * Returns `null` when no threshold reaches the target, which is a real answer:
69
+ * it means this question cannot be automated at that accuracy and the honest
70
+ * move is to change the question rather than the threshold.
71
+ */
72
+ export function recommendForAccuracy(observations, targetAccuracy, steps = 101) {
73
+ if (!(targetAccuracy >= 0 && targetAccuracy <= 1)) {
74
+ throw new RangeError("targetAccuracy must be in [0, 1]");
75
+ }
76
+ const candidates = sweep(observations, steps).filter((p) => p.covered > 0 && p.accuracy >= targetAccuracy);
77
+ if (!candidates.length)
78
+ return null;
79
+ return candidates.reduce((best, p) => (p.threshold < best.threshold ? p : best));
80
+ }
81
+ /**
82
+ * Highest threshold still covering `minCoverage`, so accuracy is best.
83
+ *
84
+ * The mirror of `recommendForAccuracy`: use it when throughput is the binding
85
+ * constraint and you want the most accurate threshold that still keeps enough
86
+ * volume out of the review queue.
87
+ */
88
+ export function recommendForCoverage(observations, minCoverage, steps = 101) {
89
+ if (!(minCoverage >= 0 && minCoverage <= 1)) {
90
+ throw new RangeError("minCoverage must be in [0, 1]");
91
+ }
92
+ const candidates = sweep(observations, steps).filter((p) => p.coverage >= minCoverage);
93
+ if (!candidates.length)
94
+ return null;
95
+ return candidates.reduce((best, p) => (p.threshold > best.threshold ? p : best));
96
+ }
97
+ //# sourceMappingURL=thresholds.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thresholds.js","sourceRoot":"","sources":["../src/thresholds.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "jevkit-calibrate",
3
+ "version": "0.1.0",
4
+ "description": "Verify TypeSafe Jev's calibration on your own data. Reliability diagrams, ECE, Brier, and confidence thresholds derived from labeled outcomes.",
5
+ "keywords": [
6
+ "jev",
7
+ "typesafe",
8
+ "system-one",
9
+ "calibration",
10
+ "ece",
11
+ "reliability",
12
+ "thresholds"
13
+ ],
14
+ "license": "MIT",
15
+ "type": "module",
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "default": "./dist/index.js"
22
+ }
23
+ },
24
+ "bin": {
25
+ "jevkit-calibrate": "./dist/cli.js"
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "README.md"
30
+ ],
31
+ "engines": {
32
+ "node": ">=18"
33
+ },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/pjdurden/jevkit-js.git"
37
+ },
38
+ "dependencies": {
39
+ "jevkit-core": "^0.2.0"
40
+ },
41
+ "scripts": {
42
+ "build": "tsc -b"
43
+ }
44
+ }