jev-recipes 0.8.3 → 0.8.5

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 CHANGED
@@ -31,7 +31,21 @@ For example, give `route` a support message and descriptions of your teams. It r
31
31
 
32
32
  Evaluate your own cases with the [installed evaluator](https://github.com/agencyenterprise/jev-recipes/blob/main/docs/evaluation.md), retain model responses, and replay confidence policies offline. The [agent workflow](https://github.com/agencyenterprise/jev-recipes/blob/main/examples/agent-loop/README.md) and [customer queue](https://github.com/agencyenterprise/jev-recipes/blob/main/examples/customer-queue/README.md) show how decisions fit into application code. [Direct and Gateway integrations](https://github.com/agencyenterprise/jev-recipes/blob/main/docs/integrations.md) use the same recipe interface.
33
33
 
34
- Build the searchable static catalog with `npm run site:build`, then preview it with `npm run site:preview`. It includes fixture exploration, related-recipe comparisons, and saved evaluation evidence. Current measurements and missing or older evidence are labeled explicitly.
34
+ ## Build a complete support flow
35
+
36
+ The [support conversation starter](https://github.com/agencyenterprise/jev-recipes/blob/main/examples/support-routing/README.md) checks for missing details, asks a configured question, and resumes with the customer's answer. It proposes a queue or requests human review when the conversation remains unresolved.
37
+
38
+ ```sh
39
+ cd examples/support-routing
40
+ npm ci --ignore-scripts
41
+ npm run dev
42
+ ```
43
+
44
+ Open `http://localhost:3000` and choose **Ask and continue**. This folder is portable and needs no repository build. Edit `config.mjs` to supply your queues, required information, questions, and review policy. Run `npm run demo` for the command-line version. Saved scenarios work without a key; the starter guide explains live calls and application integration.
45
+
46
+ The existing [rules/Jev/fallback comparison](https://github.com/agencyenterprise/jev-recipes/blob/main/evals/support-routing/README.md) covers single-turn routing, not this clarification loop. Its evidence remains experimental.
47
+
48
+ Build the searchable static catalog with `npm run site:build`, then preview it with `npm run site:preview`. It includes fixture exploration, related-recipe comparisons, and saved evaluation evidence. Current measurements and missing, unknown-origin, or older evidence are labeled explicitly. Use `npm run eval:audit` after building to check retained evidence offline; see [evaluation and archive format](https://github.com/agencyenterprise/jev-recipes/blob/main/docs/evaluation.md#archive-format-and-evidence-origin).
35
49
 
36
50
  ## Hear a real application: Jevthoven
37
51
 
@@ -1,10 +1,13 @@
1
1
  export interface RecipeEvidence {
2
- kind: 'fixture' | 'earlier' | 'synthetic' | 'public-dataset' | 'human-reviewed' | 'mixed' | 'unspecified';
2
+ kind: 'fixture' | 'unknown' | 'earlier' | 'synthetic' | 'public-dataset' | 'human-reviewed' | 'mixed' | 'unspecified';
3
3
  label: string;
4
4
  experimental: boolean;
5
5
  measurement: {
6
6
  model: string;
7
7
  date: string | null;
8
+ operation?: string | null;
9
+ replayedAt?: string | null;
10
+ scoringRevision?: number;
8
11
  split: string | null;
9
12
  cases: number;
10
13
  ready: number | null;
@@ -45,12 +45,15 @@ export const recipeMetadata = [
45
45
  },
46
46
  ],
47
47
  evidence: {
48
- kind: 'synthetic',
49
- label: 'Current synthetic measurement',
50
- experimental: false,
48
+ kind: 'earlier',
49
+ label: 'Earlier-evaluator measurement',
50
+ experimental: true,
51
51
  measurement: {
52
52
  model: 'typesafe-ai/jev',
53
53
  date: '2026-09-27T21:19:58.394Z',
54
+ operation: 'live',
55
+ replayedAt: null,
56
+ scoringRevision: 1,
54
57
  split: 'held-out',
55
58
  cases: 40,
56
59
  ready: 20,
@@ -86,21 +89,10 @@ export const recipeMetadata = [
86
89
  },
87
90
  ],
88
91
  evidence: {
89
- kind: 'earlier',
90
- label: 'Earlier-version measurement',
92
+ kind: 'unknown',
93
+ label: 'Unknown response origin',
91
94
  experimental: true,
92
- measurement: {
93
- model: 'jev-1.13.0',
94
- date: null,
95
- split: null,
96
- cases: 44,
97
- ready: null,
98
- reviewRate: null,
99
- readyAccuracy: null,
100
- failed: null,
101
- provenance: [],
102
- acceptanceMet: null,
103
- },
95
+ measurement: null,
104
96
  },
105
97
  },
106
98
  {
@@ -306,21 +298,10 @@ export const recipeMetadata = [
306
298
  },
307
299
  ],
308
300
  evidence: {
309
- kind: 'earlier',
310
- label: 'Earlier-version measurement',
301
+ kind: 'unknown',
302
+ label: 'Unknown response origin',
311
303
  experimental: true,
312
- measurement: {
313
- model: 'jev-1.13.0',
314
- date: null,
315
- split: null,
316
- cases: 50,
317
- ready: null,
318
- reviewRate: null,
319
- readyAccuracy: null,
320
- failed: null,
321
- provenance: [],
322
- acceptanceMet: null,
323
- },
304
+ measurement: null,
324
305
  },
325
306
  },
326
307
  {
@@ -583,12 +564,15 @@ export const recipeMetadata = [
583
564
  },
584
565
  ],
585
566
  evidence: {
586
- kind: 'synthetic',
587
- label: 'Current synthetic measurement',
588
- experimental: false,
567
+ kind: 'earlier',
568
+ label: 'Earlier-evaluator measurement',
569
+ experimental: true,
589
570
  measurement: {
590
571
  model: 'jev-1.13.0',
591
572
  date: '2026-09-27T07:56:26.559Z',
573
+ operation: 'live',
574
+ replayedAt: null,
575
+ scoringRevision: 1,
592
576
  split: 'held-out',
593
577
  cases: 25,
594
578
  ready: 24,
@@ -648,12 +632,15 @@ export const recipeMetadata = [
648
632
  { id: 'followup-timing', reason: 'Use followup-timing for when another contact is wanted.' },
649
633
  ],
650
634
  evidence: {
651
- kind: 'synthetic',
652
- label: 'Current synthetic measurement',
653
- experimental: false,
635
+ kind: 'earlier',
636
+ label: 'Earlier-evaluator measurement',
637
+ experimental: true,
654
638
  measurement: {
655
639
  model: 'jev-1.13.0',
656
640
  date: '2026-09-27T07:56:29.897Z',
641
+ operation: 'live',
642
+ replayedAt: null,
643
+ scoringRevision: 1,
657
644
  split: 'held-out',
658
645
  cases: 40,
659
646
  ready: 24,
@@ -906,21 +893,10 @@ export const recipeMetadata = [
906
893
  },
907
894
  ],
908
895
  evidence: {
909
- kind: 'earlier',
910
- label: 'Earlier-version measurement',
896
+ kind: 'unknown',
897
+ label: 'Unknown response origin',
911
898
  experimental: true,
912
- measurement: {
913
- model: 'jev-1.13.0',
914
- date: null,
915
- split: null,
916
- cases: 31,
917
- ready: null,
918
- reviewRate: null,
919
- readyAccuracy: null,
920
- failed: null,
921
- provenance: [],
922
- acceptanceMet: null,
923
- },
899
+ measurement: null,
924
900
  },
925
901
  },
926
902
  {
@@ -963,21 +939,10 @@ export const recipeMetadata = [
963
939
  },
964
940
  ],
965
941
  evidence: {
966
- kind: 'earlier',
967
- label: 'Earlier-version measurement',
942
+ kind: 'unknown',
943
+ label: 'Unknown response origin',
968
944
  experimental: true,
969
- measurement: {
970
- model: 'jev-1.13.0',
971
- date: null,
972
- split: null,
973
- cases: 32,
974
- ready: null,
975
- reviewRate: null,
976
- readyAccuracy: null,
977
- failed: null,
978
- provenance: [],
979
- acceptanceMet: null,
980
- },
945
+ measurement: null,
981
946
  },
982
947
  },
983
948
  {
@@ -1081,21 +1046,10 @@ export const recipeMetadata = [
1081
1046
  },
1082
1047
  ],
1083
1048
  evidence: {
1084
- kind: 'earlier',
1085
- label: 'Earlier-version measurement',
1049
+ kind: 'unknown',
1050
+ label: 'Unknown response origin',
1086
1051
  experimental: true,
1087
- measurement: {
1088
- model: 'jev-1.13.0',
1089
- date: null,
1090
- split: null,
1091
- cases: 53,
1092
- ready: null,
1093
- reviewRate: null,
1094
- readyAccuracy: null,
1095
- failed: null,
1096
- provenance: [],
1097
- acceptanceMet: null,
1098
- },
1052
+ measurement: null,
1099
1053
  },
1100
1054
  },
1101
1055
  {
@@ -1116,21 +1070,10 @@ export const recipeMetadata = [
1116
1070
  },
1117
1071
  ],
1118
1072
  evidence: {
1119
- kind: 'earlier',
1120
- label: 'Earlier-version measurement',
1073
+ kind: 'unknown',
1074
+ label: 'Unknown response origin',
1121
1075
  experimental: true,
1122
- measurement: {
1123
- model: 'jev-1.13.0',
1124
- date: null,
1125
- split: null,
1126
- cases: 50,
1127
- ready: null,
1128
- reviewRate: null,
1129
- readyAccuracy: null,
1130
- failed: null,
1131
- provenance: [],
1132
- acceptanceMet: null,
1133
- },
1076
+ measurement: null,
1134
1077
  },
1135
1078
  },
1136
1079
  {
@@ -1298,12 +1241,15 @@ export const recipeMetadata = [
1298
1241
  },
1299
1242
  ],
1300
1243
  evidence: {
1301
- kind: 'synthetic',
1302
- label: 'Current synthetic measurement',
1303
- experimental: false,
1244
+ kind: 'earlier',
1245
+ label: 'Earlier-evaluator measurement',
1246
+ experimental: true,
1304
1247
  measurement: {
1305
1248
  model: 'typesafe-ai/jev',
1306
1249
  date: '2026-09-27T21:20:01.789Z',
1250
+ operation: 'live',
1251
+ replayedAt: null,
1252
+ scoringRevision: 1,
1307
1253
  split: 'held-out',
1308
1254
  cases: 40,
1309
1255
  ready: 30,
@@ -1449,12 +1395,15 @@ export const recipeMetadata = [
1449
1395
  },
1450
1396
  ],
1451
1397
  evidence: {
1452
- kind: 'synthetic',
1453
- label: 'Current synthetic measurement',
1454
- experimental: false,
1398
+ kind: 'earlier',
1399
+ label: 'Earlier-evaluator measurement',
1400
+ experimental: true,
1455
1401
  measurement: {
1456
1402
  model: 'jev-1.13.0',
1457
1403
  date: '2026-09-27T07:56:27.401Z',
1404
+ operation: 'live',
1405
+ replayedAt: null,
1406
+ scoringRevision: 1,
1458
1407
  split: 'held-out',
1459
1408
  cases: 40,
1460
1409
  ready: 33,
@@ -1545,12 +1494,15 @@ export const recipeMetadata = [
1545
1494
  },
1546
1495
  ],
1547
1496
  evidence: {
1548
- kind: 'synthetic',
1549
- label: 'Current synthetic measurement',
1497
+ kind: 'earlier',
1498
+ label: 'Earlier-evaluator measurement',
1550
1499
  experimental: true,
1551
1500
  measurement: {
1552
1501
  model: 'typesafe-ai/jev',
1553
1502
  date: '2026-09-27T21:21:18.930Z',
1503
+ operation: 'live',
1504
+ replayedAt: null,
1505
+ scoringRevision: 1,
1554
1506
  split: 'held-out',
1555
1507
  cases: 20,
1556
1508
  ready: 11,
@@ -1778,21 +1730,10 @@ export const recipeMetadata = [
1778
1730
  },
1779
1731
  ],
1780
1732
  evidence: {
1781
- kind: 'earlier',
1782
- label: 'Earlier-version measurement',
1733
+ kind: 'unknown',
1734
+ label: 'Unknown response origin',
1783
1735
  experimental: true,
1784
- measurement: {
1785
- model: 'jev-1.13.0',
1786
- date: null,
1787
- split: null,
1788
- cases: 43,
1789
- ready: null,
1790
- reviewRate: null,
1791
- readyAccuracy: null,
1792
- failed: null,
1793
- provenance: [],
1794
- acceptanceMet: null,
1795
- },
1736
+ measurement: null,
1796
1737
  },
1797
1738
  },
1798
1739
  {
@@ -1972,12 +1913,15 @@ export const recipeMetadata = [
1972
1913
  },
1973
1914
  ],
1974
1915
  evidence: {
1975
- kind: 'synthetic',
1976
- label: 'Current synthetic measurement',
1977
- experimental: false,
1916
+ kind: 'earlier',
1917
+ label: 'Earlier-evaluator measurement',
1918
+ experimental: true,
1978
1919
  measurement: {
1979
1920
  model: 'jev-1.13.0',
1980
1921
  date: '2026-09-27T07:56:33.689Z',
1922
+ operation: 'live',
1923
+ replayedAt: null,
1924
+ scoringRevision: 1,
1981
1925
  split: 'held-out',
1982
1926
  cases: 40,
1983
1927
  ready: 30,
@@ -2268,12 +2212,15 @@ export const recipeMetadata = [
2268
2212
  },
2269
2213
  ],
2270
2214
  evidence: {
2271
- kind: 'synthetic',
2272
- label: 'Current synthetic measurement',
2273
- experimental: false,
2215
+ kind: 'earlier',
2216
+ label: 'Earlier-evaluator measurement',
2217
+ experimental: true,
2274
2218
  measurement: {
2275
2219
  model: 'jev-1.13.0',
2276
2220
  date: '2026-09-27T07:56:32.456Z',
2221
+ operation: 'live',
2222
+ replayedAt: null,
2223
+ scoringRevision: 1,
2277
2224
  split: 'held-out',
2278
2225
  cases: 40,
2279
2226
  ready: 40,
@@ -2412,12 +2359,15 @@ export const recipeMetadata = [
2412
2359
  },
2413
2360
  ],
2414
2361
  evidence: {
2415
- kind: 'synthetic',
2416
- label: 'Current synthetic measurement',
2417
- experimental: false,
2362
+ kind: 'earlier',
2363
+ label: 'Earlier-evaluator measurement',
2364
+ experimental: true,
2418
2365
  measurement: {
2419
2366
  model: 'jev-1.13.0',
2420
2367
  date: '2026-09-27T07:56:31.169Z',
2368
+ operation: 'live',
2369
+ replayedAt: null,
2370
+ scoringRevision: 1,
2421
2371
  split: 'held-out',
2422
2372
  cases: 40,
2423
2373
  ready: 30,
@@ -2501,12 +2451,15 @@ export const recipeMetadata = [
2501
2451
  },
2502
2452
  ],
2503
2453
  evidence: {
2504
- kind: 'synthetic',
2505
- label: 'Current synthetic measurement',
2506
- experimental: false,
2454
+ kind: 'earlier',
2455
+ label: 'Earlier-evaluator measurement',
2456
+ experimental: true,
2507
2457
  measurement: {
2508
2458
  model: 'jev-1.13.0',
2509
2459
  date: '2026-09-27T07:56:24.179Z',
2460
+ operation: 'live',
2461
+ replayedAt: null,
2462
+ scoringRevision: 1,
2510
2463
  split: 'held-out',
2511
2464
  cases: 40,
2512
2465
  ready: 30,
@@ -2551,12 +2504,15 @@ export const recipeMetadata = [
2551
2504
  },
2552
2505
  ],
2553
2506
  evidence: {
2554
- kind: 'synthetic',
2555
- label: 'Current synthetic measurement',
2556
- experimental: false,
2507
+ kind: 'earlier',
2508
+ label: 'Earlier-evaluator measurement',
2509
+ experimental: true,
2557
2510
  measurement: {
2558
2511
  model: 'jev-1.13.0',
2559
2512
  date: '2026-09-27T07:56:28.731Z',
2513
+ operation: 'live',
2514
+ replayedAt: null,
2515
+ scoringRevision: 1,
2560
2516
  split: 'held-out',
2561
2517
  cases: 40,
2562
2518
  ready: 37,
@@ -2750,21 +2706,10 @@ export const recipeMetadata = [
2750
2706
  },
2751
2707
  ],
2752
2708
  evidence: {
2753
- kind: 'earlier',
2754
- label: 'Earlier-version measurement',
2709
+ kind: 'unknown',
2710
+ label: 'Unknown response origin',
2755
2711
  experimental: true,
2756
- measurement: {
2757
- model: 'jev-1.13.0',
2758
- date: null,
2759
- split: null,
2760
- cases: 44,
2761
- ready: null,
2762
- reviewRate: null,
2763
- readyAccuracy: null,
2764
- failed: null,
2765
- provenance: [],
2766
- acceptanceMet: null,
2767
- },
2712
+ measurement: null,
2768
2713
  },
2769
2714
  },
2770
2715
  {
@@ -2828,12 +2773,15 @@ export const recipeMetadata = [
2828
2773
  { id: 'route', reason: 'Use route to choose a handler when escalation is not the decision.' },
2829
2774
  ],
2830
2775
  evidence: {
2831
- kind: 'synthetic',
2832
- label: 'Current synthetic measurement',
2833
- experimental: false,
2776
+ kind: 'earlier',
2777
+ label: 'Earlier-evaluator measurement',
2778
+ experimental: true,
2834
2779
  measurement: {
2835
2780
  model: 'jev-1.13.0',
2836
2781
  date: '2026-09-27T07:56:21.635Z',
2782
+ operation: 'live',
2783
+ replayedAt: null,
2784
+ scoringRevision: 1,
2837
2785
  split: 'held-out',
2838
2786
  cases: 40,
2839
2787
  ready: 28,
@@ -2958,21 +2906,10 @@ export const recipeMetadata = [
2958
2906
  },
2959
2907
  ],
2960
2908
  evidence: {
2961
- kind: 'earlier',
2962
- label: 'Earlier-version measurement',
2909
+ kind: 'unknown',
2910
+ label: 'Unknown response origin',
2963
2911
  experimental: true,
2964
- measurement: {
2965
- model: 'jev-1.13.0',
2966
- date: null,
2967
- split: null,
2968
- cases: 44,
2969
- ready: null,
2970
- reviewRate: null,
2971
- readyAccuracy: null,
2972
- failed: null,
2973
- provenance: [],
2974
- acceptanceMet: null,
2975
- },
2912
+ measurement: null,
2976
2913
  },
2977
2914
  },
2978
2915
  {
@@ -3686,12 +3623,15 @@ export const recipeMetadata = [
3686
3623
  },
3687
3624
  ],
3688
3625
  evidence: {
3689
- kind: 'synthetic',
3690
- label: 'Current synthetic measurement',
3691
- experimental: false,
3626
+ kind: 'earlier',
3627
+ label: 'Earlier-evaluator measurement',
3628
+ experimental: true,
3692
3629
  measurement: {
3693
3630
  model: 'jev-1.13.0',
3694
3631
  date: '2026-09-27T07:56:10.135Z',
3632
+ operation: 'live',
3633
+ replayedAt: null,
3634
+ scoringRevision: 1,
3695
3635
  split: 'held-out',
3696
3636
  cases: 40,
3697
3637
  ready: 26,
@@ -4118,12 +4058,15 @@ export const recipeMetadata = [
4118
4058
  },
4119
4059
  ],
4120
4060
  evidence: {
4121
- kind: 'public-dataset',
4122
- label: 'Current public-dataset measurement',
4061
+ kind: 'earlier',
4062
+ label: 'Earlier-evaluator measurement',
4123
4063
  experimental: true,
4124
4064
  measurement: {
4125
4065
  model: 'typesafe-ai/jev',
4126
4066
  date: '2026-09-27T21:21:33.447Z',
4067
+ operation: 'live',
4068
+ replayedAt: null,
4069
+ scoringRevision: 1,
4127
4070
  split: 'held-out',
4128
4071
  cases: 167,
4129
4072
  ready: 82,
@@ -4446,21 +4389,10 @@ export const recipeMetadata = [
4446
4389
  },
4447
4390
  ],
4448
4391
  evidence: {
4449
- kind: 'earlier',
4450
- label: 'Earlier-version measurement',
4392
+ kind: 'unknown',
4393
+ label: 'Unknown response origin',
4451
4394
  experimental: true,
4452
- measurement: {
4453
- model: 'jev-1.13.0',
4454
- date: null,
4455
- split: null,
4456
- cases: 43,
4457
- ready: null,
4458
- reviewRate: null,
4459
- readyAccuracy: null,
4460
- failed: null,
4461
- provenance: [],
4462
- acceptanceMet: null,
4463
- },
4395
+ measurement: null,
4464
4396
  },
4465
4397
  },
4466
4398
  {
@@ -4693,12 +4625,15 @@ export const recipeMetadata = [
4693
4625
  },
4694
4626
  ],
4695
4627
  evidence: {
4696
- kind: 'synthetic',
4697
- label: 'Current synthetic measurement',
4698
- experimental: false,
4628
+ kind: 'earlier',
4629
+ label: 'Earlier-evaluator measurement',
4630
+ experimental: true,
4699
4631
  measurement: {
4700
4632
  model: 'jev-1.13.0',
4701
4633
  date: '2026-09-27T07:56:14.022Z',
4634
+ operation: 'live',
4635
+ replayedAt: null,
4636
+ scoringRevision: 1,
4702
4637
  split: 'held-out',
4703
4638
  cases: 40,
4704
4639
  ready: 40,
@@ -4733,12 +4668,15 @@ export const recipeMetadata = [
4733
4668
  },
4734
4669
  ],
4735
4670
  evidence: {
4736
- kind: 'synthetic',
4737
- label: 'Current synthetic measurement',
4738
- experimental: false,
4671
+ kind: 'earlier',
4672
+ label: 'Earlier-evaluator measurement',
4673
+ experimental: true,
4739
4674
  measurement: {
4740
4675
  model: 'jev-1.13.0',
4741
4676
  date: '2026-09-27T07:56:25.450Z',
4677
+ operation: 'live',
4678
+ replayedAt: null,
4679
+ scoringRevision: 1,
4742
4680
  split: 'held-out',
4743
4681
  cases: 40,
4744
4682
  ready: 40,
@@ -5117,21 +5055,10 @@ export const recipeMetadata = [
5117
5055
  },
5118
5056
  ],
5119
5057
  evidence: {
5120
- kind: 'earlier',
5121
- label: 'Earlier-version measurement',
5058
+ kind: 'unknown',
5059
+ label: 'Unknown response origin',
5122
5060
  experimental: true,
5123
- measurement: {
5124
- model: 'jev-1.13.0',
5125
- date: null,
5126
- split: null,
5127
- cases: 57,
5128
- ready: null,
5129
- reviewRate: null,
5130
- readyAccuracy: null,
5131
- failed: null,
5132
- provenance: [],
5133
- acceptanceMet: null,
5134
- },
5061
+ measurement: null,
5135
5062
  },
5136
5063
  },
5137
5064
  {
@@ -5173,12 +5100,15 @@ export const recipeMetadata = [
5173
5100
  },
5174
5101
  ],
5175
5102
  evidence: {
5176
- kind: 'synthetic',
5177
- label: 'Current synthetic measurement',
5178
- experimental: false,
5103
+ kind: 'earlier',
5104
+ label: 'Earlier-evaluator measurement',
5105
+ experimental: true,
5179
5106
  measurement: {
5180
5107
  model: 'jev-1.13.0',
5181
5108
  date: '2026-09-27T07:56:22.956Z',
5109
+ operation: 'live',
5110
+ replayedAt: null,
5111
+ scoringRevision: 1,
5182
5112
  split: 'held-out',
5183
5113
  cases: 40,
5184
5114
  ready: 40,
@@ -5231,21 +5161,10 @@ export const recipeMetadata = [
5231
5161
  },
5232
5162
  ],
5233
5163
  evidence: {
5234
- kind: 'earlier',
5235
- label: 'Earlier-version measurement',
5164
+ kind: 'unknown',
5165
+ label: 'Unknown response origin',
5236
5166
  experimental: true,
5237
- measurement: {
5238
- model: 'jev-1.13.0',
5239
- date: null,
5240
- split: null,
5241
- cases: 44,
5242
- ready: null,
5243
- reviewRate: null,
5244
- readyAccuracy: null,
5245
- failed: null,
5246
- provenance: [],
5247
- acceptanceMet: null,
5248
- },
5167
+ measurement: null,
5249
5168
  },
5250
5169
  },
5251
5170
  {
@@ -5347,12 +5266,15 @@ export const recipeMetadata = [
5347
5266
  },
5348
5267
  ],
5349
5268
  evidence: {
5350
- kind: 'synthetic',
5351
- label: 'Current synthetic measurement',
5352
- experimental: false,
5269
+ kind: 'earlier',
5270
+ label: 'Earlier-evaluator measurement',
5271
+ experimental: true,
5353
5272
  measurement: {
5354
5273
  model: 'jev-1.13.0',
5355
5274
  date: '2026-09-27T07:56:15.276Z',
5275
+ operation: 'live',
5276
+ replayedAt: null,
5277
+ scoringRevision: 1,
5356
5278
  split: 'held-out',
5357
5279
  cases: 40,
5358
5280
  ready: 39,
@@ -5535,12 +5457,15 @@ export const recipeMetadata = [
5535
5457
  },
5536
5458
  ],
5537
5459
  evidence: {
5538
- kind: 'synthetic',
5539
- label: 'Current synthetic measurement',
5540
- experimental: false,
5460
+ kind: 'earlier',
5461
+ label: 'Earlier-evaluator measurement',
5462
+ experimental: true,
5541
5463
  measurement: {
5542
5464
  model: 'jev-1.13.0',
5543
5465
  date: '2026-09-27T07:56:19.034Z',
5466
+ operation: 'live',
5467
+ replayedAt: null,
5468
+ scoringRevision: 1,
5544
5469
  split: 'held-out',
5545
5470
  cases: 40,
5546
5471
  ready: 24,
@@ -5580,12 +5505,15 @@ export const recipeMetadata = [
5580
5505
  },
5581
5506
  ],
5582
5507
  evidence: {
5583
- kind: 'synthetic',
5584
- label: 'Current synthetic measurement',
5585
- experimental: false,
5508
+ kind: 'earlier',
5509
+ label: 'Earlier-version measurement',
5510
+ experimental: true,
5586
5511
  measurement: {
5587
5512
  model: 'jev-1.13.0',
5588
5513
  date: '2026-09-27T07:56:20.238Z',
5514
+ operation: 'live',
5515
+ replayedAt: null,
5516
+ scoringRevision: 1,
5589
5517
  split: 'held-out',
5590
5518
  cases: 40,
5591
5519
  ready: 25,
@@ -6204,12 +6132,15 @@ export const recipeMetadata = [
6204
6132
  },
6205
6133
  ],
6206
6134
  evidence: {
6207
- kind: 'public-dataset',
6208
- label: 'Current public-dataset measurement',
6135
+ kind: 'earlier',
6136
+ label: 'Earlier-evaluator measurement',
6209
6137
  experimental: true,
6210
6138
  measurement: {
6211
6139
  model: 'typesafe-ai/jev',
6212
6140
  date: '2026-09-27T21:21:20.879Z',
6141
+ operation: 'live',
6142
+ replayedAt: null,
6143
+ scoringRevision: 1,
6213
6144
  split: 'held-out',
6214
6145
  cases: 189,
6215
6146
  ready: 136,
@@ -6404,12 +6335,15 @@ export const recipeMetadata = [
6404
6335
  },
6405
6336
  ],
6406
6337
  evidence: {
6407
- kind: 'synthetic',
6408
- label: 'Current synthetic measurement',
6409
- experimental: false,
6338
+ kind: 'earlier',
6339
+ label: 'Earlier-evaluator measurement',
6340
+ experimental: true,
6410
6341
  measurement: {
6411
6342
  model: 'jev-1.13.0',
6412
6343
  date: '2026-09-27T07:56:16.449Z',
6344
+ operation: 'live',
6345
+ replayedAt: null,
6346
+ scoringRevision: 1,
6413
6347
  split: 'held-out',
6414
6348
  cases: 40,
6415
6349
  ready: 31,
@@ -6618,21 +6552,10 @@ export const recipeMetadata = [
6618
6552
  },
6619
6553
  ],
6620
6554
  evidence: {
6621
- kind: 'earlier',
6622
- label: 'Earlier-version measurement',
6555
+ kind: 'unknown',
6556
+ label: 'Unknown response origin',
6623
6557
  experimental: true,
6624
- measurement: {
6625
- model: 'jev-1.13.0',
6626
- date: null,
6627
- split: null,
6628
- cases: 50,
6629
- ready: null,
6630
- reviewRate: null,
6631
- readyAccuracy: null,
6632
- failed: null,
6633
- provenance: [],
6634
- acceptanceMet: null,
6635
- },
6558
+ measurement: null,
6636
6559
  },
6637
6560
  },
6638
6561
  {
@@ -6657,21 +6580,10 @@ export const recipeMetadata = [
6657
6580
  },
6658
6581
  ],
6659
6582
  evidence: {
6660
- kind: 'earlier',
6661
- label: 'Earlier-version measurement',
6583
+ kind: 'unknown',
6584
+ label: 'Unknown response origin',
6662
6585
  experimental: true,
6663
- measurement: {
6664
- model: 'jev-1.13.0',
6665
- date: null,
6666
- split: null,
6667
- cases: 44,
6668
- ready: null,
6669
- reviewRate: null,
6670
- readyAccuracy: null,
6671
- failed: null,
6672
- provenance: [],
6673
- acceptanceMet: null,
6674
- },
6586
+ measurement: null,
6675
6587
  },
6676
6588
  },
6677
6589
  {
@@ -4,7 +4,13 @@ import type { EvaluationRow } from './engine.js';
4
4
  import { buildReport } from './report.js';
5
5
  import type { EvaluationOptions, EvaluationPolicy } from './schema.js';
6
6
  declare const archiveSchema: z.ZodObject<{
7
- format: z.ZodLiteral<1>;
7
+ format: z.ZodLiteral<2>;
8
+ scoringRevision: z.ZodLiteral<2>;
9
+ sourceMode: z.ZodEnum<{
10
+ fixture: "fixture";
11
+ live: "live";
12
+ }>;
13
+ evaluatedAt: z.ZodISODateTime;
8
14
  runId: z.ZodString;
9
15
  createdAt: z.ZodISODateTime;
10
16
  mode: z.ZodEnum<{
@@ -125,6 +131,7 @@ export declare function compare(baseline: EvaluationRun, candidate: EvaluationRu
125
131
  } | undefined;
126
132
  };
127
133
  mode: "fixture" | "live" | "replay";
134
+ scoringRevision: 2;
128
135
  report: {
129
136
  contestedAccuracy: number | null;
130
137
  adversarialAccuracy: number | null;
@@ -310,6 +317,7 @@ export declare function compare(baseline: EvaluationRun, candidate: EvaluationRu
310
317
  } | undefined;
311
318
  };
312
319
  mode: "fixture" | "live" | "replay";
320
+ scoringRevision: 2;
313
321
  report: {
314
322
  contestedAccuracy: number | null;
315
323
  adversarialAccuracy: number | null;
@@ -7,8 +7,13 @@ import { datasetFingerprints, fingerprint, loadEvaluationRecipe, recipeFingerpri
7
7
  import { confidenceThresholds, evaluateCases } from './engine.js';
8
8
  import { buildReport } from './report.js';
9
9
  import { evaluationCaseSchema, evaluationOptionsSchema, evaluationPolicySchema, priceSchema, } from './schema.js';
10
- const archiveSchema = z.object({
11
- format: z.literal(1),
10
+ import { scoringRevision } from './comparison.js';
11
+ const archiveSchema = z
12
+ .object({
13
+ format: z.literal(2),
14
+ scoringRevision: z.literal(scoringRevision),
15
+ sourceMode: z.enum(['live', 'fixture']),
16
+ evaluatedAt: z.iso.datetime(),
12
17
  runId: z.string(),
13
18
  createdAt: z.iso.datetime(),
14
19
  mode: z.enum(['live', 'fixture', 'replay']),
@@ -53,6 +58,21 @@ const archiveSchema = z.object({
53
58
  thresholds: z.array(z.number()),
54
59
  sourceRun: z.string().optional(),
55
60
  report: z.record(z.string(), z.unknown()),
61
+ })
62
+ .superRefine((run, context) => {
63
+ if (run.mode !== 'replay' && (run.sourceMode !== run.mode || run.evaluatedAt !== run.createdAt))
64
+ context.addIssue({
65
+ code: 'custom',
66
+ message: 'Direct runs must retain their own response mode and date.',
67
+ });
68
+ for (const [rowIndex, row] of run.rows.entries())
69
+ for (const [exchangeIndex, exchange] of row.exchanges.entries())
70
+ if (Object.hasOwn(exchange, 'response') === Object.hasOwn(exchange, 'error'))
71
+ context.addIssue({
72
+ code: 'custom',
73
+ path: ['rows', rowIndex, 'exchanges', exchangeIndex],
74
+ message: 'A completed exchange must contain exactly one response or error.',
75
+ });
56
76
  });
57
77
  export async function evaluate(name, values, options = {}) {
58
78
  const settings = evaluationOptionsSchema.parse(options);
@@ -73,10 +93,14 @@ export async function evaluate(name, values, options = {}) {
73
93
  throw new Error('The frozen policy belongs to a different recipe version or requested model.');
74
94
  }
75
95
  const manifest = JSON.parse(await readFile(new URL('../../package.json', import.meta.url), 'utf8'));
96
+ const createdAt = new Date().toISOString();
76
97
  const metadata = {
77
- format: 1,
98
+ format: 2,
99
+ scoringRevision,
100
+ sourceMode: options.mode ?? 'live',
101
+ evaluatedAt: createdAt,
78
102
  runId: randomUUID(),
79
- createdAt: new Date().toISOString(),
103
+ createdAt,
80
104
  mode: options.mode ?? 'live',
81
105
  packageVersion: manifest.version,
82
106
  recipe: recipe.id,
@@ -130,6 +154,8 @@ export async function replay(run, policy = run.policy, out) {
130
154
  const cases = validateCases(recipe, run.cases);
131
155
  const metadata = {
132
156
  ...run,
157
+ format: 2,
158
+ scoringRevision,
133
159
  runId: randomUUID(),
134
160
  createdAt: new Date().toISOString(),
135
161
  mode: 'replay',
@@ -158,8 +184,10 @@ export async function readRun(directory) {
158
184
  throw error;
159
185
  return gunzipSync(await readFile(join(directory, 'run.json.gz'))).toString('utf8');
160
186
  });
161
- const parsed = archiveSchema.parse(JSON.parse(content));
162
- const run = parsed;
187
+ const raw = JSON.parse(content);
188
+ if (raw.format !== 2)
189
+ throw new Error('Unsupported archive format. This evaluator reads format 2; historical files remain available for direct inspection.');
190
+ const run = archiveSchema.parse(raw);
163
191
  const hashes = datasetFingerprints(run.cases);
164
192
  if (Object.entries(hashes).some(([key, hash]) => run[key] !== hash))
165
193
  throw new Error('Archive dataset fingerprints do not match its cases.');
@@ -196,7 +224,10 @@ function reportEvidence(run) {
196
224
  }
197
225
  return {
198
226
  runId: run.runId,
199
- evaluatedAt: run.createdAt,
227
+ sourceMode: run.sourceMode,
228
+ evaluatedAt: run.evaluatedAt,
229
+ createdAt: run.createdAt,
230
+ scoringRevision: run.scoringRevision,
200
231
  packageVersion: run.packageVersion,
201
232
  recipeFingerprint: run.recipeFingerprint,
202
233
  datasetFingerprint: run.datasetFingerprint,
@@ -207,6 +238,8 @@ function reportEvidence(run) {
207
238
  };
208
239
  }
209
240
  export function compare(baseline, candidate) {
241
+ if (baseline.scoringRevision !== candidate.scoringRevision)
242
+ throw new Error('Comparison requires the same scoring revision. Replay compatible archives before comparing.');
210
243
  if (baseline.recipe !== candidate.recipe ||
211
244
  baseline.inputFingerprint !== candidate.inputFingerprint ||
212
245
  baseline.answerKeyFingerprint !== candidate.answerKeyFingerprint)
@@ -220,6 +253,7 @@ export function compare(baseline, candidate) {
220
253
  models: run.report.models,
221
254
  policy: run.policy,
222
255
  mode: run.mode,
256
+ scoringRevision: run.scoringRevision,
223
257
  report: run.report,
224
258
  });
225
259
  return {
@@ -0,0 +1,8 @@
1
+ export declare const scoringRevision: 2;
2
+ type Schema = Record<string, unknown>;
3
+ export declare function resultSchemaFor(recipe: string): Schema;
4
+ export declare function comparableDecision(value: unknown, recipe: string): unknown;
5
+ export declare function matchesExpected(actual: unknown, expected: unknown, recipe: string): boolean;
6
+ export declare function isMetadataPath(schema: Schema, segments: string[]): boolean;
7
+ export declare function schemaAtPath(schema: Schema, segments: string[]): Schema | undefined;
8
+ export {};
@@ -0,0 +1,83 @@
1
+ import { isDeepStrictEqual } from 'node:util';
2
+ import { describeRecipe } from '../catalog/index.js';
3
+ import { recipeNameSchema } from '../catalog/schema.js';
4
+ import { isRecord } from './decisions.js';
5
+ export const scoringRevision = 2;
6
+ const metadataFields = new Set([
7
+ 'model',
8
+ 'usage',
9
+ 'confidence',
10
+ 'probability',
11
+ 'probabilities',
12
+ 'score',
13
+ 'status',
14
+ ]);
15
+ const resultSchemas = new Map();
16
+ export function resultSchemaFor(recipe) {
17
+ const cached = resultSchemas.get(recipe);
18
+ if (cached)
19
+ return cached;
20
+ const schema = describeRecipe(recipeNameSchema.parse(recipe)).resultSchema;
21
+ resultSchemas.set(recipe, schema);
22
+ return schema;
23
+ }
24
+ // Expected keys are result paths. Below an unconstrained payload, every key is data.
25
+ export function comparableDecision(value, recipe) {
26
+ if (!isRecord(value))
27
+ return value;
28
+ const schema = resultSchemaFor(recipe);
29
+ return Object.fromEntries(Object.entries(value)
30
+ .filter(([path]) => !isMetadataPath(schema, path.split('.')))
31
+ .map(([path, field]) => [path, normalize(field, schemaAtPath(schema, path.split('.')))]));
32
+ }
33
+ export function matchesExpected(actual, expected, recipe) {
34
+ return isDeepStrictEqual(comparableDecision(actual, recipe), comparableDecision(expected, recipe));
35
+ }
36
+ function normalize(value, schema) {
37
+ if (!schema)
38
+ return value;
39
+ if (Array.isArray(value))
40
+ return value.map((item, index) => normalize(item, schemaAtPath(schema, [String(index)])));
41
+ if (!isRecord(value))
42
+ return value;
43
+ return Object.fromEntries(Object.entries(value)
44
+ .filter(([name]) => !isMetadataPath(schema, [name]))
45
+ .map(([name, field]) => [name, normalize(field, schemaAtPath(schema, [name]))]));
46
+ }
47
+ export function isMetadataPath(schema, segments) {
48
+ if (!segments.length)
49
+ return false;
50
+ if (Array.isArray(schema.anyOf))
51
+ return schema.anyOf.some((branch) => isRecord(branch) && isMetadataPath(branch, segments));
52
+ const [first, ...rest] = segments;
53
+ if (isRecord(schema.properties) &&
54
+ Object.hasOwn(schema.properties, first) &&
55
+ metadataFields.has(first))
56
+ return true;
57
+ const child = schemaAtPath(schema, [first]);
58
+ return child !== undefined && isMetadataPath(child, rest);
59
+ }
60
+ export function schemaAtPath(schema, segments) {
61
+ if (!segments.length)
62
+ return schema;
63
+ if (Array.isArray(schema.anyOf)) {
64
+ const candidates = schema.anyOf.flatMap((branch) => {
65
+ const field = isRecord(branch) ? schemaAtPath(branch, segments) : undefined;
66
+ return field ? [field] : [];
67
+ });
68
+ return candidates.length ? { anyOf: candidates } : undefined;
69
+ }
70
+ const [first, ...rest] = segments;
71
+ if (isRecord(schema.properties) &&
72
+ Object.hasOwn(schema.properties, first) &&
73
+ isRecord(schema.properties[first]))
74
+ return schemaAtPath(schema.properties[first], rest);
75
+ if (schema.type === 'array' && /^(0|[1-9]\d*)$/.test(first) && isRecord(schema.items))
76
+ return schemaAtPath(schema.items, rest);
77
+ if (isRecord(schema.additionalProperties))
78
+ return schemaAtPath(schema.additionalProperties, rest);
79
+ if (schema.additionalProperties === true ||
80
+ (schema.type === undefined && schema.enum === undefined && schema.const === undefined))
81
+ return {};
82
+ return undefined;
83
+ }
@@ -1,9 +1,9 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { readFile } from 'node:fs/promises';
3
3
  import { z } from 'zod';
4
- import { describeRecipe } from '../catalog/index.js';
5
4
  import { recipeNameSchema } from '../catalog/schema.js';
6
- import { isRecord, comparableDecision } from './decisions.js';
5
+ import { isRecord } from './decisions.js';
6
+ import { comparableDecision, isMetadataPath, schemaAtPath, resultSchemaFor } from './comparison.js';
7
7
  import { evaluationCaseSchema } from './schema.js';
8
8
  export async function loadEvaluationRecipe(name) {
9
9
  const id = recipeNameSchema.parse(name);
@@ -30,7 +30,7 @@ export async function readEvaluationCases(path) {
30
30
  export function validateCases(recipe, values) {
31
31
  if (!values.length)
32
32
  throw new Error('The case file is empty.');
33
- const resultSchema = describeRecipe(recipeNameSchema.parse(recipe.id)).resultSchema;
33
+ const resultSchema = resultSchemaFor(recipe.id);
34
34
  const cases = values.map((value) => evaluationCaseSchema.parse(value));
35
35
  const ids = new Set();
36
36
  const familySplits = new Map();
@@ -45,17 +45,7 @@ export function validateCases(recipe, values) {
45
45
  }
46
46
  recipe.inputSchema.parse(entry.input);
47
47
  for (const [path, expected] of Object.entries(entry.expected)) {
48
- if (path
49
- .split('.')
50
- .some((segment) => [
51
- 'status',
52
- 'confidence',
53
- 'probabilities',
54
- 'probability',
55
- 'model',
56
- 'usage',
57
- 'score',
58
- ].includes(segment)))
48
+ if (isMetadataPath(resultSchema, path.split('.')))
59
49
  throw new Error(`${entry.id}: expected must name an ungated decision, not ${path}.`);
60
50
  const field = schemaAtPath(resultSchema, path.split('.'));
61
51
  if (!field)
@@ -63,30 +53,11 @@ export function validateCases(recipe, values) {
63
53
  if (!acceptsValue(field, expected))
64
54
  throw new Error(`${entry.id}: expected ${path} does not match the result schema.`);
65
55
  }
66
- if (isEmptyDecision(comparableDecision(entry.expected)))
56
+ if (isEmptyDecision(comparableDecision(entry.expected, recipe.id)))
67
57
  throw new Error(`${entry.id}: expected must contain a decision to compare.`);
68
58
  }
69
59
  return cases;
70
60
  }
71
- function schemaAtPath(schema, segments) {
72
- if (!segments.length)
73
- return schema;
74
- if (Array.isArray(schema.anyOf)) {
75
- const candidates = schema.anyOf.flatMap((branch) => {
76
- const field = isRecord(branch) ? schemaAtPath(branch, segments) : undefined;
77
- return field ? [field] : [];
78
- });
79
- return candidates.length ? { anyOf: candidates } : undefined;
80
- }
81
- const [first, ...rest] = segments;
82
- if (isRecord(schema.properties) && isRecord(schema.properties[first]))
83
- return schemaAtPath(schema.properties[first], rest);
84
- if (schema.type === 'array' && /^(0|[1-9]\d*)$/.test(first) && isRecord(schema.items))
85
- return schemaAtPath(schema.items, rest);
86
- if (isRecord(schema.additionalProperties))
87
- return schemaAtPath(schema.additionalProperties, rest);
88
- return undefined;
89
- }
90
61
  function acceptsValue(schema, value) {
91
62
  if (Array.isArray(schema.anyOf))
92
63
  return schema.anyOf.some((candidate) => isRecord(candidate) && acceptsValue(candidate, value));
@@ -1,5 +1,3 @@
1
- export declare function comparableDecision(value: unknown): unknown;
2
- export declare function matchesExpected(actual: unknown, expected: unknown): boolean;
3
1
  export declare function isRecord(value: unknown): value is Record<string, unknown>;
4
2
  export declare function valueAtPath(record: unknown, path: string): unknown;
5
3
  export declare function pickPaths(record: unknown, paths: string[]): Record<string, unknown>;
@@ -1,25 +1,3 @@
1
- import { isDeepStrictEqual } from 'node:util';
2
- const metadataFields = new Set([
3
- 'model',
4
- 'usage',
5
- 'confidence',
6
- 'probability',
7
- 'probabilities',
8
- 'score',
9
- 'status',
10
- ]);
11
- export function comparableDecision(value) {
12
- if (Array.isArray(value))
13
- return value.map(comparableDecision);
14
- if (isRecord(value))
15
- return Object.fromEntries(Object.entries(value)
16
- .filter(([name]) => !metadataFields.has(name))
17
- .map(([name, field]) => [name, comparableDecision(field)]));
18
- return value;
19
- }
20
- export function matchesExpected(actual, expected) {
21
- return isDeepStrictEqual(comparableDecision(actual), comparableDecision(expected));
22
- }
23
1
  export function isRecord(value) {
24
2
  return value !== null && typeof value === 'object' && !Array.isArray(value);
25
3
  }
@@ -1,6 +1,7 @@
1
1
  import { isDeepStrictEqual } from 'node:util';
2
2
  import { createClient } from '../src/client.js';
3
- import { caseConfidence, isReady, mapWithConcurrencyLimit, matchesExpected, pickPaths, } from './decisions.js';
3
+ import { caseConfidence, isReady, mapWithConcurrencyLimit, pickPaths } from './decisions.js';
4
+ import { matchesExpected } from './comparison.js';
4
5
  export const confidenceThresholds = [0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95];
5
6
  export async function evaluateCases(recipe, cases, options = {}) {
6
7
  let requests = 0;
@@ -50,7 +51,7 @@ async function evaluateCase(recipe, goldenCase, client, options) {
50
51
  try {
51
52
  row.result = await recipe.run(goldenCase.input, recipeOptions);
52
53
  row.actual = pickPaths(row.result, Object.keys(goldenCase.expected));
53
- row.correct = matchesExpected(row.actual, row.expected);
54
+ row.correct = matchesExpected(row.actual, row.expected, recipe.id);
54
55
  row.ready = isReady(row.result);
55
56
  row.confidence = caseConfidence(row.result, Object.keys(goldenCase.expected));
56
57
  const model = pickPaths(row.result, ['model']).model;
@@ -72,7 +73,7 @@ async function evaluateCase(recipe, goldenCase, client, options) {
72
73
  row.decisions.push({
73
74
  minConfidence,
74
75
  ready: isReady(result),
75
- correct: matchesExpected(pickPaths(result, Object.keys(row.expected)), row.expected),
76
+ correct: matchesExpected(pickPaths(result, Object.keys(row.expected)), row.expected, recipe.id),
76
77
  });
77
78
  }
78
79
  return row;
@@ -4,6 +4,9 @@ import type { EvaluationCase, EvaluationPolicy } from './schema.js';
4
4
  export interface ReportEvidence {
5
5
  runId: string;
6
6
  evaluatedAt: string;
7
+ createdAt: string;
8
+ sourceMode: 'live' | 'fixture';
9
+ scoringRevision: number;
7
10
  packageVersion: string;
8
11
  recipeFingerprint: string;
9
12
  datasetFingerprint: string;
@@ -1,4 +1,5 @@
1
- import { isRecord, matchesExpected, roundedTo } from './decisions.js';
1
+ import { isRecord, roundedTo } from './decisions.js';
2
+ import { matchesExpected } from './comparison.js';
2
3
  export function buildReport(id, rows, confidenceThresholds, price, evidence) {
3
4
  const completed = rows.filter((row) => row.error === undefined);
4
5
  const ready = completed.filter((row) => row.ready);
@@ -41,7 +42,7 @@ export function buildReport(id, rows, confidenceThresholds, price, evidence) {
41
42
  reviewRate: completed.length
42
43
  ? roundedTo(3, (completed.length - ready.length) / completed.length)
43
44
  : null,
44
- ...itemAccuracyOf(rows),
45
+ ...itemAccuracyOf(id, rows),
45
46
  contestedAccuracy: accuracyOf(rows.filter((row) => row.contested)),
46
47
  adversarialAccuracy: accuracyOf(rows.filter((row) => row.adversarial)),
47
48
  calibration: [0, 0.5, 0.6, 0.7, 0.8, 0.9].map((lower, index, edges) => {
@@ -90,14 +91,15 @@ export function buildReport(id, rows, confidenceThresholds, price, evidence) {
90
91
  function accuracyOf(rows) {
91
92
  return rows.length ? roundedTo(3, rows.filter((row) => row.correct).length / rows.length) : null;
92
93
  }
93
- function itemAccuracyOf(rows) {
94
+ function itemAccuracyOf(recipe, rows) {
94
95
  const paths = rows.flatMap((row) => Object.entries(row.expected).map(([path, expected]) => ({
96
+ path,
95
97
  expected,
96
98
  actual: row.error === undefined ? row.actual?.[path] : undefined,
97
99
  })));
98
100
  if (paths.length <= rows.length)
99
101
  return {};
100
- const correct = paths.filter((entry) => matchesExpected(entry.actual, entry.expected)).length;
102
+ const correct = paths.filter(({ path, actual, expected }) => matchesExpected({ [path]: actual }, { [path]: expected }, recipe)).length;
101
103
  return {
102
104
  items: paths.length,
103
105
  correctItems: correct,
package/dist/src/batch.js CHANGED
@@ -11,7 +11,20 @@ export async function evaluateInBatches(items, batchSize, evaluate) {
11
11
  const chunks = [];
12
12
  for (let offset = 0; offset < items.length; offset += batchSize)
13
13
  chunks.push({ chunk: items.slice(offset, offset + batchSize), offset });
14
- const evaluations = await Promise.all(chunks.map(({ chunk, offset }) => evaluate(chunk, offset)));
14
+ let firstFailure;
15
+ const settled = await Promise.allSettled(chunks.map(async ({ chunk, offset }) => {
16
+ try {
17
+ return await evaluate(chunk, offset);
18
+ }
19
+ catch (error) {
20
+ firstFailure ??= { error };
21
+ throw error;
22
+ }
23
+ }));
24
+ // Let request recorders finish before a caller archives this failed batch.
25
+ if (firstFailure)
26
+ throw firstFailure.error;
27
+ const evaluations = settled.flatMap((result) => result.status === 'fulfilled' ? [result.value] : []);
15
28
  return {
16
29
  items: evaluations.flatMap((evaluation) => evaluation.items),
17
30
  model: evaluations[0]?.model ?? 'none',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jev-recipes",
3
- "version": "0.8.3",
3
+ "version": "0.8.5",
4
4
  "description": "Small, composable Jev decisions for retrieval, conversations, and agent workflows.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1054,6 +1054,7 @@
1054
1054
  "eval": "node --env-file-if-exists=.env evals/run.mjs",
1055
1055
  "eval:smoke": "node --env-file-if-exists=.env evals/smoke.mjs",
1056
1056
  "eval:validate": "node evals/validate.mjs",
1057
+ "eval:audit": "node scripts/audit-evidence.mjs",
1057
1058
  "jev-recipes": "node --env-file-if-exists=.env dist/cli/index.js",
1058
1059
  "ci": "node scripts/ci.mjs",
1059
1060
  "pack:check": "node scripts/pack-check.mjs",