@crowandraven/evalkeeper 0.0.0-stage → 0.1.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.
Files changed (151) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -2
  3. package/bin/evalkeeper.mjs +9 -0
  4. package/dist/attention.d.ts +49 -0
  5. package/dist/attention.js +48 -0
  6. package/dist/attention.js.map +1 -0
  7. package/dist/board.d.ts +55 -0
  8. package/dist/board.js +2 -0
  9. package/dist/board.js.map +1 -0
  10. package/dist/cli/addPages.d.ts +79 -0
  11. package/dist/cli/addPages.js +181 -0
  12. package/dist/cli/addPages.js.map +1 -0
  13. package/dist/cli/main.d.ts +18 -0
  14. package/dist/cli/main.js +115 -0
  15. package/dist/cli/main.js.map +1 -0
  16. package/dist/common.d.ts +72 -0
  17. package/dist/common.js +276 -0
  18. package/dist/common.js.map +1 -0
  19. package/dist/compare.d.ts +85 -0
  20. package/dist/compare.js +185 -0
  21. package/dist/compare.js.map +1 -0
  22. package/dist/decisions.d.ts +84 -0
  23. package/dist/decisions.js +146 -0
  24. package/dist/decisions.js.map +1 -0
  25. package/dist/feedback.d.ts +114 -0
  26. package/dist/feedback.js +116 -0
  27. package/dist/feedback.js.map +1 -0
  28. package/dist/format.d.ts +6 -0
  29. package/dist/format.js +22 -0
  30. package/dist/format.js.map +1 -0
  31. package/dist/glossary.d.ts +46 -0
  32. package/dist/glossary.js +45 -0
  33. package/dist/glossary.js.map +1 -0
  34. package/dist/health.d.ts +40 -0
  35. package/dist/health.js +101 -0
  36. package/dist/health.js.map +1 -0
  37. package/dist/index.d.ts +31 -0
  38. package/dist/index.js +34 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/labels.d.ts +8 -0
  41. package/dist/labels.js +15 -0
  42. package/dist/labels.js.map +1 -0
  43. package/dist/legs.d.ts +23 -0
  44. package/dist/legs.js +22 -0
  45. package/dist/legs.js.map +1 -0
  46. package/dist/memoryStore.d.ts +21 -0
  47. package/dist/memoryStore.js +228 -0
  48. package/dist/memoryStore.js.map +1 -0
  49. package/dist/models.d.ts +50 -0
  50. package/dist/models.js +39 -0
  51. package/dist/models.js.map +1 -0
  52. package/dist/prune.d.ts +48 -0
  53. package/dist/prune.js +77 -0
  54. package/dist/prune.js.map +1 -0
  55. package/dist/quickSet.d.ts +26 -0
  56. package/dist/quickSet.js +44 -0
  57. package/dist/quickSet.js.map +1 -0
  58. package/dist/runner.d.ts +193 -0
  59. package/dist/runner.js +706 -0
  60. package/dist/runner.js.map +1 -0
  61. package/dist/settings.d.ts +157 -0
  62. package/dist/settings.js +183 -0
  63. package/dist/settings.js.map +1 -0
  64. package/dist/stableJson.d.ts +11 -0
  65. package/dist/stableJson.js +15 -0
  66. package/dist/stableJson.js.map +1 -0
  67. package/dist/stats.d.ts +98 -0
  68. package/dist/stats.js +119 -0
  69. package/dist/stats.js.map +1 -0
  70. package/dist/store.d.ts +146 -0
  71. package/dist/store.js +3 -0
  72. package/dist/store.js.map +1 -0
  73. package/dist/suggest.d.ts +75 -0
  74. package/dist/suggest.js +150 -0
  75. package/dist/suggest.js.map +1 -0
  76. package/dist/summary.d.ts +51 -0
  77. package/dist/summary.js +61 -0
  78. package/dist/summary.js.map +1 -0
  79. package/dist/testing.d.ts +26 -0
  80. package/dist/testing.js +341 -0
  81. package/dist/testing.js.map +1 -0
  82. package/dist/timeout.d.ts +2 -0
  83. package/dist/timeout.js +15 -0
  84. package/dist/timeout.js.map +1 -0
  85. package/dist/types.d.ts +219 -0
  86. package/dist/types.js +46 -0
  87. package/dist/types.js.map +1 -0
  88. package/dist/upkeepRules.d.ts +28 -0
  89. package/dist/upkeepRules.js +40 -0
  90. package/dist/upkeepRules.js.map +1 -0
  91. package/dist/util.d.ts +8 -0
  92. package/dist/util.js +18 -0
  93. package/dist/util.js.map +1 -0
  94. package/dist/weekly.d.ts +80 -0
  95. package/dist/weekly.js +111 -0
  96. package/dist/weekly.js.map +1 -0
  97. package/package.json +81 -4
  98. package/src/attention.ts +88 -0
  99. package/src/board.ts +56 -0
  100. package/src/cli/addPages.ts +256 -0
  101. package/src/cli/main.ts +125 -0
  102. package/src/common.ts +338 -0
  103. package/src/compare.ts +261 -0
  104. package/src/decisions.ts +230 -0
  105. package/src/feedback.ts +225 -0
  106. package/src/format.ts +19 -0
  107. package/src/glossary.ts +75 -0
  108. package/src/health.ts +147 -0
  109. package/src/index.ts +33 -0
  110. package/src/labels.ts +18 -0
  111. package/src/legs.ts +38 -0
  112. package/src/memoryStore.ts +268 -0
  113. package/src/models.ts +82 -0
  114. package/src/prune.ts +95 -0
  115. package/src/quickSet.ts +55 -0
  116. package/src/runner.ts +971 -0
  117. package/src/settings.ts +331 -0
  118. package/src/stableJson.ts +24 -0
  119. package/src/stats.ts +215 -0
  120. package/src/store.ts +167 -0
  121. package/src/suggest.ts +245 -0
  122. package/src/summary.ts +109 -0
  123. package/src/testing.ts +392 -0
  124. package/src/timeout.ts +14 -0
  125. package/src/types.ts +252 -0
  126. package/src/upkeepRules.ts +46 -0
  127. package/src/util.ts +17 -0
  128. package/src/weekly.ts +167 -0
  129. package/starter/index.ts +86 -0
  130. package/templates/app/api/cron/evals-weekly/route.ts +24 -0
  131. package/templates/app/evals/[suite]/page.tsx +449 -0
  132. package/templates/app/evals/[suite]/runs/[runId]/page.tsx +95 -0
  133. package/templates/app/evals/feedback/page.tsx +219 -0
  134. package/templates/app/evals/page.tsx +313 -0
  135. package/templates/components/evalkeeper/AddCase.tsx +62 -0
  136. package/templates/components/evalkeeper/AnswerKey.tsx +360 -0
  137. package/templates/components/evalkeeper/CaseMenu.tsx +122 -0
  138. package/templates/components/evalkeeper/Fields.tsx +110 -0
  139. package/templates/components/evalkeeper/RefreshWhile.tsx +25 -0
  140. package/templates/components/evalkeeper/SettingsHistory.tsx +191 -0
  141. package/templates/components/evalkeeper/Submit.tsx +58 -0
  142. package/templates/components/evalkeeper/Thumbs.tsx +117 -0
  143. package/templates/components/evalkeeper/Tip.tsx +98 -0
  144. package/templates/components/evalkeeper/WeeklyCheck.tsx +124 -0
  145. package/templates/components/evalkeeper/evalkeeper.css +71 -0
  146. package/templates/components/evalkeeper/ui.tsx +103 -0
  147. package/templates/lib/evalkeeper/actions.ts +263 -0
  148. package/templates/lib/evalkeeper/cronAuth.ts +18 -0
  149. package/templates/lib/evalkeeper/paths.ts +11 -0
  150. package/templates/lib/evalkeeper/time.ts +4 -0
  151. package/templates/lib/evalkeeper/weekly.ts +51 -0
@@ -0,0 +1,72 @@
1
+ import type { CheckDef, CheckResult } from "./types.js";
2
+ /** Text that refers to the person as he, she, or they; null when none. */
3
+ export declare function thirdPerson(text: string): string | null;
4
+ /**
5
+ * Standard phrasing whose numbers aren't claims about the person: a "first
6
+ * 90 days" plan, "30-60-90", "2 to 3 minutes", a "top 5", a quarter (Q3),
7
+ * fact ids ("[f9]"), and list markers ("(1)", "2.").
8
+ */
9
+ export declare function withoutStockPhrases(text: string): string;
10
+ /**
11
+ * Number words that count something in an answer ("twenty years", "a
12
+ * decade", "five direct reports") as digits, so an invented figure written
13
+ * out is caught too. Number words that count nothing ("one idea", "two
14
+ * things to try") stay words.
15
+ */
16
+ export declare function withCountedWords(text: string): string;
17
+ /**
18
+ * Numbers the text states that appear in none of the sources: likely invented.
19
+ * Standard phrasing (a 30-60-90 day plan, list markers, fact ids) doesn't count.
20
+ */
21
+ export declare function inventedNumbers(text: string, sources: string[]): string[];
22
+ /** How many words the text has. */
23
+ export declare function wordCount(text: string): number;
24
+ /** Sentences, counted by their ending punctuation (a last one without it counts). */
25
+ export declare function sentenceCount(text: string): number;
26
+ /** Pass when n is within [min, max]; the note says what it was. */
27
+ export declare function inRange(key: string, n: number, min: number, max: number, noun: string): CheckResult;
28
+ /** The "addressed as you" check over several pieces of text. */
29
+ export declare function voiceCheck(key: string, texts: string[]): CheckResult;
30
+ /** The "no invented numbers" check over several pieces of text. */
31
+ export declare function numbersCheck(key: string, texts: string[], sources: string[]): CheckResult;
32
+ /**
33
+ * A minor check that answers speak to the person ("you"), not about them.
34
+ */
35
+ export declare const VOICE_CHECK: CheckDef;
36
+ /**
37
+ * A serious check that every number in an answer appears in what the model was given.
38
+ */
39
+ export declare const NUMBERS_CHECK: CheckDef;
40
+ /** Numbers in text, with "150k" and "1.5m" also read as 150000 and 1500000. */
41
+ export declare function numbersIn(text: string): Set<string>;
42
+ /**
43
+ * Number words up to ninety-nine as digits ("sixteen weeks" → "16 weeks",
44
+ * "twenty-one" → "21"), for the evidence side of a number check: sources
45
+ * often spell numbers out where answers use digits.
46
+ */
47
+ export declare function withNumberWords(text: string): string;
48
+ /**
49
+ * A word's stem for matching: words of 5 letters or more lose one common
50
+ * ending ("training", "trained" → "train"); shorter words and codes ("AR",
51
+ * "CPT") match as written, so "AR" never matches "are".
52
+ */
53
+ export declare function stem(word: string): string;
54
+ /**
55
+ * Whether every word of `phrase` appears in `text`, by stem ("trained new
56
+ * staff" has "training"). "a|b" accepts either phrase.
57
+ */
58
+ export declare function hasPhrase(text: string, phrase: string): boolean;
59
+ /** Words that carry meaning, stemmed: for matching by shared words. */
60
+ export declare function contentWords(text: string): Set<string>;
61
+ /**
62
+ * Pairs expected items with actual ones by shared content words, best
63
+ * overlap first, one to one. The fallback when no judge model is set: it
64
+ * misses paraphrases, so a meaning judge is better.
65
+ */
66
+ export declare function wordPairs(expected: string[], actual: string[],
67
+ /** Share of an expected item's words the actual one must have. */
68
+ minShare?: number): {
69
+ expected: number;
70
+ actual: number;
71
+ why: string;
72
+ }[];
package/dist/common.js ADDED
@@ -0,0 +1,276 @@
1
+ /**
2
+ * Checks shared by suites: rules many operations' instructions state the
3
+ * same way (lib/ai/voice.ts, "don't invent numbers"). Pure, so they're
4
+ * unit-tested and rerun on stored outputs for free.
5
+ */
6
+ // He, she, and the like never belong in text addressed to the person.
7
+ // They, them, and their often mean things, so they count only when they
8
+ // open a sentence the way writing about the person would ("They want").
9
+ const PRONOUNS = /\b(he|she|him|her|his|hers|himself|herself)\b/i;
10
+ const THIRD_PERSON_START = /(^|[.!?]\s+)(they|their|them)\b/i;
11
+ /** Text that refers to the person as he, she, or they; null when none. */
12
+ export function thirdPerson(text) {
13
+ const m = text.match(PRONOUNS) ?? text.match(THIRD_PERSON_START);
14
+ return m ? m[0].trim().replace(/^[.!?]\s*/, "") : null;
15
+ }
16
+ /**
17
+ * Standard phrasing whose numbers aren't claims about the person: a "first
18
+ * 90 days" plan, "30-60-90", "2 to 3 minutes", a "top 5", a quarter (Q3),
19
+ * fact ids ("[f9]"), and list markers ("(1)", "2.").
20
+ */
21
+ export function withoutStockPhrases(text) {
22
+ return (text
23
+ .replace(/\b30\s*[-–/]\s*60\s*[-–/]\s*90\b/g, " ")
24
+ .replace(/\b(?:first|next|initial)\s+(?:30|60|90|100)\s+days\b/gi, " ")
25
+ .replace(/\b(?:30|60|90)\s+days?\s*[:\-–]/gi, " ")
26
+ .replace(/\b\d+\s*(?:to|-|–|or)\s*\d+\s*(?:minutes?|mins?|sentences?|seconds?)\b/gi, " ")
27
+ .replace(/\b(?:under|about|in|within)\s+\d+\s+(?:minutes?|seconds?)\b/gi, " ")
28
+ .replace(/\btop\s+(?:3|5|10)\b/gi, " ")
29
+ .replace(/\bQ[1-4]\b/g, " ")
30
+ // References, not figures: fact ids ("[f9]", "f10") and list markers.
31
+ .replace(/\bf\d+\b/g, " ")
32
+ .replace(/\(\d{1,2}\)/g, " ")
33
+ .replace(/(^|\n)\s*\d{1,2}[.)]\s/g, "$1 "));
34
+ }
35
+ /** Nouns a number counts in a claim ("twenty years", "five reports"). */
36
+ const COUNTED = "years?|months?|weeks?|hours?|people|employees|staff|clients|customers|direct reports|reports|students|patients|accounts|projects|sites|schools|percent";
37
+ const NUMBER_WORDS = "(?:(?:twenty|thirty|forty|fifty|sixty|seventy|eighty|ninety)(?:[\\s-](?:one|two|three|four|five|six|seven|eight|nine))?|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|two|three|four|five|six|seven|eight|nine|ten)";
38
+ /**
39
+ * Number words that count something in an answer ("twenty years", "a
40
+ * decade", "five direct reports") as digits, so an invented figure written
41
+ * out is caught too. Number words that count nothing ("one idea", "two
42
+ * things to try") stay words.
43
+ */
44
+ export function withCountedWords(text) {
45
+ return text
46
+ .replace(/\b(?:a|one)\s+decade\b/gi, "10 years")
47
+ .replace(new RegExp(`\\b${NUMBER_WORDS}(?=\\s+(?:${COUNTED})\\b)`, "gi"), (w) => withNumberWords(w));
48
+ }
49
+ /**
50
+ * Numbers in `text` that appear in none of the sources (150k and 150,000
51
+ * count as the same). Years and small counts are numbers too: a letter
52
+ * that says "12 years" when the profile says 10 has invented one.
53
+ */
54
+ /**
55
+ * A number with an optional "k" or "m" ("150k", "1.5m"). It starts only
56
+ * where a number starts and reads it once (the lookahead and backreference
57
+ * keep the digits from being retried), so long digit runs in model output
58
+ * stay linear. A number glued to a word ("3rd", "10am") doesn't count.
59
+ */
60
+ const NUMBER = /(?<![\d,.])(?=(\d[\d,]*(?:\.\d+)?))\1\s*([km])?\b/gi;
61
+ /**
62
+ * Numbers the text states that appear in none of the sources: likely invented.
63
+ * Standard phrasing (a 30-60-90 day plan, list markers, fact ids) doesn't count.
64
+ */
65
+ export function inventedNumbers(text, sources) {
66
+ const known = new Set(sources.flatMap((s) => [...numbersIn(withNumberWords(s))]));
67
+ const out = [];
68
+ const claims = withCountedWords(withoutStockPhrases(text));
69
+ for (const m of claims.matchAll(NUMBER)) {
70
+ const found = numbersIn(m[0]);
71
+ if (![...found].some((n) => known.has(n)))
72
+ out.push(m[0].trim());
73
+ }
74
+ return [...new Set(out)];
75
+ }
76
+ /** How many words the text has. */
77
+ export function wordCount(text) {
78
+ return text.split(/\s+/).filter(Boolean).length;
79
+ }
80
+ /** Sentences, counted by their ending punctuation (a last one without it counts). */
81
+ export function sentenceCount(text) {
82
+ const t = text.trim();
83
+ if (!t)
84
+ return 0;
85
+ // The last mark before a space or the end, so "?!" counts once. Linear:
86
+ // a run of marks is never retried from each position.
87
+ const ends = t.match(/[.!?](?=\s|$)/g)?.length ?? 0;
88
+ return /[.!?]["')\]]?$/.test(t) ? ends : ends + 1;
89
+ }
90
+ /** Pass when n is within [min, max]; the note says what it was. */
91
+ export function inRange(key, n, min, max, noun) {
92
+ const pass = n >= min && n <= max;
93
+ return {
94
+ key,
95
+ pass,
96
+ ...(pass
97
+ ? {}
98
+ : {
99
+ note: `${n} ${noun}; should be ${min === max ? min : `${min} to ${max}`}`,
100
+ }),
101
+ };
102
+ }
103
+ /** The "addressed as you" check over several pieces of text. */
104
+ export function voiceCheck(key, texts) {
105
+ for (const t of texts) {
106
+ const hit = thirdPerson(t);
107
+ if (hit)
108
+ return { key, pass: false, note: `"${hit}" in: ${t}` };
109
+ }
110
+ return { key, pass: true };
111
+ }
112
+ /** The "no invented numbers" check over several pieces of text. */
113
+ export function numbersCheck(key, texts, sources) {
114
+ const invented = [
115
+ ...new Set(texts.flatMap((t) => inventedNumbers(t, sources))),
116
+ ];
117
+ return invented.length
118
+ ? {
119
+ key,
120
+ pass: false,
121
+ note: `Not in what it was given: ${invented.join(", ")}`,
122
+ }
123
+ : { key, pass: true };
124
+ }
125
+ /**
126
+ * A minor check that answers speak to the person ("you"), not about them.
127
+ */
128
+ export const VOICE_CHECK = {
129
+ key: "voice",
130
+ label: 'Addressed as "you"',
131
+ description: 'Never "he", "she", or "they" for the person (lib/ai/voice.ts): they read it about themselves.',
132
+ kind: "code",
133
+ severity: "serious",
134
+ };
135
+ /**
136
+ * A serious check that every number in an answer appears in what the model was given.
137
+ */
138
+ export const NUMBERS_CHECK = {
139
+ key: "numbers",
140
+ label: "No invented numbers",
141
+ description: "Every number appears in what the model was given (150k and 150,000 count as the same).",
142
+ kind: "code",
143
+ severity: "serious",
144
+ };
145
+ /** Numbers in text, with "150k" and "1.5m" also read as 150000 and 1500000. */
146
+ export function numbersIn(text) {
147
+ const out = new Set();
148
+ for (const m of text.matchAll(NUMBER)) {
149
+ const n = Number((m[1] ?? "").replace(/,/g, ""));
150
+ if (!Number.isFinite(n))
151
+ continue;
152
+ out.add(String(n));
153
+ const suffix = m[2]?.toLowerCase();
154
+ if (suffix === "k")
155
+ out.add(String(Math.round(n * 1_000)));
156
+ if (suffix === "m")
157
+ out.add(String(Math.round(n * 1_000_000)));
158
+ }
159
+ return out;
160
+ }
161
+ const ONES = [
162
+ "zero",
163
+ "one",
164
+ "two",
165
+ "three",
166
+ "four",
167
+ "five",
168
+ "six",
169
+ "seven",
170
+ "eight",
171
+ "nine",
172
+ "ten",
173
+ "eleven",
174
+ "twelve",
175
+ "thirteen",
176
+ "fourteen",
177
+ "fifteen",
178
+ "sixteen",
179
+ "seventeen",
180
+ "eighteen",
181
+ "nineteen",
182
+ ];
183
+ const TENS = [
184
+ "",
185
+ "",
186
+ "twenty",
187
+ "thirty",
188
+ "forty",
189
+ "fifty",
190
+ "sixty",
191
+ "seventy",
192
+ "eighty",
193
+ "ninety",
194
+ ];
195
+ /**
196
+ * Number words up to ninety-nine as digits ("sixteen weeks" → "16 weeks",
197
+ * "twenty-one" → "21"), for the evidence side of a number check: sources
198
+ * often spell numbers out where answers use digits.
199
+ */
200
+ export function withNumberWords(text) {
201
+ const tens = TENS.filter(Boolean).join("|");
202
+ return text
203
+ .replace(new RegExp(`\\b(${tens})[\\s-](${ONES.slice(1, 10).join("|")})\\b`, "gi"), (_, t, o) => String(TENS.indexOf(t.toLowerCase()) * 10 + ONES.indexOf(o.toLowerCase())))
204
+ .replace(new RegExp(`\\b(${tens})\\b`, "gi"), (t) => String(TENS.indexOf(t.toLowerCase()) * 10))
205
+ .replace(new RegExp(`\\b(${ONES.join("|")})\\b`, "gi"), (o) => String(ONES.indexOf(o.toLowerCase())));
206
+ }
207
+ /**
208
+ * A word's stem for matching: words of 5 letters or more lose one common
209
+ * ending ("training", "trained" → "train"); shorter words and codes ("AR",
210
+ * "CPT") match as written, so "AR" never matches "are".
211
+ */
212
+ export function stem(word) {
213
+ const w = word.toLowerCase();
214
+ if (w.length < 5)
215
+ return w;
216
+ const cut = w
217
+ .replace(/(ings|ing|ers|er|ed|es|ions|ion|ly|s)$/, "")
218
+ // "drone" and "drones" both become "dron".
219
+ .replace(/(.{4,})e$/, "$1");
220
+ return cut.length >= 4 ? cut : w;
221
+ }
222
+ const wordsOf = (text) => text.toLowerCase().match(/[a-z0-9]+/g) ?? [];
223
+ /**
224
+ * Whether every word of `phrase` appears in `text`, by stem ("trained new
225
+ * staff" has "training"). "a|b" accepts either phrase.
226
+ */
227
+ export function hasPhrase(text, phrase) {
228
+ const have = new Set(wordsOf(text).map(stem));
229
+ return phrase
230
+ .split("|")
231
+ .some((p) => wordsOf(p).every((w) => have.has(stem(w))));
232
+ }
233
+ /** Words that carry meaning, stemmed: for matching by shared words. */
234
+ export function contentWords(text) {
235
+ return new Set((text.toLowerCase().match(/[a-z0-9]+/g) ?? [])
236
+ .filter((w) => w.length > 2 && !STOP_WORDS.has(w))
237
+ .map(stem));
238
+ }
239
+ const STOP_WORDS = new Set("the and for with that this from into onto over under about after before their there them they you your our its are was were will would should could has have had not but all any can each per out off than then when what who how why".split(" "));
240
+ /**
241
+ * Pairs expected items with actual ones by shared content words, best
242
+ * overlap first, one to one. The fallback when no judge model is set: it
243
+ * misses paraphrases, so a meaning judge is better.
244
+ */
245
+ export function wordPairs(expected, actual,
246
+ /** Share of an expected item's words the actual one must have. */
247
+ minShare = 0.5) {
248
+ const scored = [];
249
+ expected.forEach((et, e) => {
250
+ const ew = contentWords(et);
251
+ if (!ew.size)
252
+ return;
253
+ actual.forEach((at, a) => {
254
+ const aw = contentWords(at);
255
+ const shared = [...ew].filter((w) => aw.has(w)).length;
256
+ if (shared / ew.size >= minShare)
257
+ scored.push({ e, a, share: shared / ew.size });
258
+ });
259
+ });
260
+ const usedE = new Set();
261
+ const usedA = new Set();
262
+ const out = [];
263
+ for (const s of scored.sort((x, y) => y.share - x.share)) {
264
+ if (usedE.has(s.e) || usedA.has(s.a))
265
+ continue;
266
+ usedE.add(s.e);
267
+ usedA.add(s.a);
268
+ out.push({
269
+ expected: s.e,
270
+ actual: s.a,
271
+ why: `${Math.round(s.share * 100)}% of the words match`,
272
+ });
273
+ }
274
+ return out.sort((x, y) => x.expected - y.expected);
275
+ }
276
+ //# sourceMappingURL=common.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"common.js","sourceRoot":"","sources":["../src/common.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,85 @@
1
+ import { type Rate } from "./stats.js";
2
+ import type { CheckResult } from "./types.js";
3
+ /**
4
+ * Comparing two runs of a suite, case by case: the champion (usually the
5
+ * live version) and a challenger (another model, effort, or instructions).
6
+ * Each case scores the share of its repeats where every check passed; the
7
+ * challenger's average difference gets a 95% range from a paired
8
+ * bootstrap, and the verdict is read off that range. Pure; no database.
9
+ */
10
+ /** One stored answer, as a comparison reads it. */
11
+ export type RunOutput = {
12
+ caseId: string;
13
+ checks: CheckResult[] | null;
14
+ costUsd: number;
15
+ ms: number;
16
+ reused: boolean;
17
+ };
18
+ /**
19
+ * How a challenger compares with the model in place: more accurate, about as accurate,
20
+ * less accurate, or too few cases to tell.
21
+ */
22
+ export type Verdict = "BETTER" | "SAME" | "WORSE" | "TOO_FEW";
23
+ /**
24
+ * Two tests compared case by case: the verdict, the difference and its range, the cases
25
+ * the challenger fixed and broke, and each check's pass rates.
26
+ */
27
+ export type Comparison = {
28
+ /** Cases both runs have. */
29
+ n: number;
30
+ /** Challenger minus champion, in pass share (−1 to 1). */
31
+ mean: number;
32
+ low: number;
33
+ high: number;
34
+ verdict: Verdict;
35
+ /** Passed every time on the challenger, not on the champion. */
36
+ fixed: string[];
37
+ /** Passed every time on the champion, not on the challenger. */
38
+ broke: string[];
39
+ byCheck: Record<string, {
40
+ champion: Rate;
41
+ challenger: Rate;
42
+ }>;
43
+ /** Per output, called (not reused). */
44
+ cost: {
45
+ champion: number | null;
46
+ challenger: number | null;
47
+ };
48
+ avgMs: {
49
+ champion: number | null;
50
+ challenger: number | null;
51
+ };
52
+ };
53
+ /** Fewer shared cases than this: no verdict. */
54
+ export declare const MIN_CASES = 5;
55
+ /** 95% range of the mean of paired differences, by resampling cases. */
56
+ export declare function bootstrapMean(diffs: number[], iterations?: number, seed?: number): {
57
+ mean: number;
58
+ low: number;
59
+ high: number;
60
+ };
61
+ /**
62
+ * The verdict for a difference's 95% range on `n` shared cases: too few under 5, else
63
+ * better or worse only when the whole range is on one side of zero.
64
+ */
65
+ export declare function verdictFor(n: number, low: number, high: number): Verdict;
66
+ /**
67
+ * 95% range of the difference in parts-right rate (b minus a), resampling
68
+ * whole cases: the same measure as the headline (parts right).
69
+ */
70
+ export declare function bootstrapRate(cases: {
71
+ a: number;
72
+ b: number;
73
+ total: number;
74
+ }[], iterations?: number, seed?: number): {
75
+ mean: number;
76
+ low: number;
77
+ high: number;
78
+ };
79
+ /**
80
+ * Compares two tests on the cases they share, with a paired bootstrap, using the same
81
+ * partial credit as the headline.
82
+ */
83
+ export declare function compareRuns(champion: RunOutput[], challenger: RunOutput[], checkKeys: string[]): Comparison;
84
+ /** The verdict in plain words, with what it costs. */
85
+ export declare function verdictSentence(c: Comparison, challengerName: string): string;
@@ -0,0 +1,185 @@
1
+ import { answerScore, wilson } from "./stats.js";
2
+ import { at } from "./util.js";
3
+ import { mustGet } from "./util.js";
4
+ /** Fewer shared cases than this: no verdict. */
5
+ export const MIN_CASES = 5;
6
+ /** A small seeded generator, so the same runs always give the same range. */
7
+ function mulberry32(seed) {
8
+ let a = seed;
9
+ return () => {
10
+ a |= 0;
11
+ a = (a + 0x6d2b79f5) | 0;
12
+ let t = Math.imul(a ^ (a >>> 15), 1 | a);
13
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
14
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
15
+ };
16
+ }
17
+ /** 95% range of the mean of paired differences, by resampling cases. */
18
+ export function bootstrapMean(diffs, iterations = 4000, seed = 1) {
19
+ const n = diffs.length;
20
+ const mean = n ? diffs.reduce((t, d) => t + d, 0) / n : 0;
21
+ if (n < 2)
22
+ return { mean, low: mean, high: mean };
23
+ const rand = mulberry32(seed);
24
+ const means = [];
25
+ for (let i = 0; i < iterations; i++) {
26
+ let sum = 0;
27
+ for (let j = 0; j < n; j++)
28
+ sum += at(diffs, Math.floor(rand() * n));
29
+ means.push(sum / n);
30
+ }
31
+ means.sort((a, b) => a - b);
32
+ return {
33
+ mean,
34
+ low: at(means, Math.floor(iterations * 0.025)),
35
+ high: at(means, Math.ceil(iterations * 0.975) - 1),
36
+ };
37
+ }
38
+ /**
39
+ * The verdict for a difference's 95% range on `n` shared cases: too few under 5, else
40
+ * better or worse only when the whole range is on one side of zero.
41
+ */
42
+ export function verdictFor(n, low, high) {
43
+ if (n < MIN_CASES)
44
+ return "TOO_FEW";
45
+ if (low > 0)
46
+ return "BETTER";
47
+ if (high < 0)
48
+ return "WORSE";
49
+ return "SAME";
50
+ }
51
+ /** Each case's average score over its repeats (partial credit if any). */
52
+ function passShareByCase(outputs) {
53
+ const by = new Map();
54
+ for (const o of outputs)
55
+ by.set(o.caseId, [...(by.get(o.caseId) ?? []), answerScore(o.checks)]);
56
+ return new Map([...by].map(([id, r]) => [id, r.reduce((t, x) => t + x, 0) / r.length]));
57
+ }
58
+ /**
59
+ * Each case's parts right and parts in all, averaged over its repeats, or
60
+ * null when the suite gives no partial credit.
61
+ */
62
+ function unitsByCase(outputs) {
63
+ const by = new Map();
64
+ for (const o of outputs) {
65
+ const u = o.checks?.find((c) => c.units)?.units;
66
+ if (o.checks && !u)
67
+ return null;
68
+ const cur = by.get(o.caseId) ?? { right: 0, total: 0, n: 0 };
69
+ // A failed call: nothing right, as many parts as the case has (known
70
+ // from another repeat or run; 0 here, set below).
71
+ by.set(o.caseId, {
72
+ right: cur.right + (u?.right ?? 0),
73
+ total: cur.total + (u?.total ?? 0),
74
+ n: cur.n + 1,
75
+ });
76
+ }
77
+ return new Map([...by].map(([id, v]) => [
78
+ id,
79
+ { right: v.right / v.n, total: v.total / v.n },
80
+ ]));
81
+ }
82
+ /**
83
+ * 95% range of the difference in parts-right rate (b minus a), resampling
84
+ * whole cases: the same measure as the headline (parts right).
85
+ */
86
+ export function bootstrapRate(cases, iterations = 4000, seed = 1) {
87
+ const rate = (cs) => {
88
+ const total = cs.reduce((t, c) => t + c.total, 0);
89
+ return total ? cs.reduce((t, c) => t + c.b - c.a, 0) / total : 0;
90
+ };
91
+ const mean = rate(cases);
92
+ if (cases.length < 2)
93
+ return { mean, low: mean, high: mean };
94
+ const rand = mulberry32(seed);
95
+ const rates = [];
96
+ for (let i = 0; i < iterations; i++) {
97
+ const sample = Array.from({ length: cases.length }, () => at(cases, Math.floor(rand() * cases.length)));
98
+ rates.push(rate(sample));
99
+ }
100
+ rates.sort((x, y) => x - y);
101
+ return {
102
+ mean,
103
+ low: at(rates, Math.floor(iterations * 0.025)),
104
+ high: at(rates, Math.ceil(iterations * 0.975) - 1),
105
+ };
106
+ }
107
+ function rates(outputs, key) {
108
+ const applied = outputs
109
+ .map((o) => o.checks?.find((c) => c.key === key))
110
+ .filter((c) => c !== undefined && c.pass !== null);
111
+ return wilson(applied.filter((c) => c.pass).length, applied.length);
112
+ }
113
+ function perCall(outputs, pick) {
114
+ const called = outputs.filter((o) => !o.reused);
115
+ return called.length
116
+ ? called.reduce((t, o) => t + pick(o), 0) / called.length
117
+ : null;
118
+ }
119
+ /**
120
+ * Compares two tests on the cases they share, with a paired bootstrap, using the same
121
+ * partial credit as the headline.
122
+ */
123
+ export function compareRuns(champion, challenger, checkKeys) {
124
+ const a = passShareByCase(champion);
125
+ const b = passShareByCase(challenger);
126
+ const shared = [...a.keys()].filter((id) => b.has(id)).sort();
127
+ // Parts right where the suite gives partial credit (the headline
128
+ // measure), else each case's share of passing answers.
129
+ const ua = unitsByCase(champion);
130
+ const ub = unitsByCase(challenger);
131
+ const { mean, low, high } = ua && ub
132
+ ? bootstrapRate(shared.map((id) => ({
133
+ a: mustGet(ua, id).right,
134
+ b: mustGet(ub, id).right,
135
+ total: Math.max(mustGet(ua, id).total, mustGet(ub, id).total),
136
+ })))
137
+ : bootstrapMean(shared.map((id) => mustGet(b, id) - mustGet(a, id)));
138
+ const inShared = (os) => os.filter((o) => shared.includes(o.caseId));
139
+ return {
140
+ n: shared.length,
141
+ mean,
142
+ low,
143
+ high,
144
+ verdict: verdictFor(shared.length, low, high),
145
+ fixed: shared.filter((id) => b.get(id) === 1 && mustGet(a, id) < 1),
146
+ broke: shared.filter((id) => a.get(id) === 1 && mustGet(b, id) < 1),
147
+ byCheck: Object.fromEntries(checkKeys.map((k) => [
148
+ k,
149
+ {
150
+ champion: rates(inShared(champion), k),
151
+ challenger: rates(inShared(challenger), k),
152
+ },
153
+ ])),
154
+ cost: {
155
+ champion: perCall(champion, (o) => o.costUsd),
156
+ challenger: perCall(challenger, (o) => o.costUsd),
157
+ },
158
+ avgMs: {
159
+ champion: perCall(champion, (o) => o.ms),
160
+ challenger: perCall(challenger, (o) => o.ms),
161
+ },
162
+ };
163
+ }
164
+ /** The verdict in plain words, with what it costs. */
165
+ export function verdictSentence(c, challengerName) {
166
+ const pts = (x) => `${Math.round(Math.abs(x) * 100)} points`;
167
+ const cost = c.cost.champion && c.cost.challenger
168
+ ? c.cost.challenger < c.cost.champion * 0.9
169
+ ? `, and costs ${Math.round((1 - c.cost.challenger / c.cost.champion) * 100)}% less per call`
170
+ : c.cost.challenger > c.cost.champion * 1.1
171
+ ? `, and costs ${Math.round((c.cost.challenger / c.cost.champion - 1) * 100)}% more per call`
172
+ : ", at about the same cost"
173
+ : "";
174
+ switch (c.verdict) {
175
+ case "TOO_FEW":
176
+ return `Only ${c.n} cases in common: too few to say. Run both on the same set of at least ${MIN_CASES} cases.`;
177
+ case "BETTER":
178
+ return `${challengerName} is better: it passes ${pts(c.mean)} more often (likely ${pts(c.low)} to ${pts(c.high)})${cost}.`;
179
+ case "WORSE":
180
+ return `${challengerName} is worse: it passes ${pts(c.mean)} less often (likely ${pts(c.high)} to ${pts(c.low)})${cost}.`;
181
+ default:
182
+ return `No clear difference: ${challengerName} is somewhere between ${Math.round(c.low * 100)} and ${c.high > 0 ? "+" : ""}${Math.round(c.high * 100)} points${cost}.`;
183
+ }
184
+ }
185
+ //# sourceMappingURL=compare.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare.js","sourceRoot":"","sources":["../src/compare.ts"],"names":[],"mappings":"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"}