@sdeverywhere/check-core 0.1.2 → 0.1.4

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/dist/index.cjs CHANGED
@@ -78,6 +78,7 @@ __export(src_exports, {
78
78
  datasetMessage: () => datasetMessage,
79
79
  diffDatasets: () => diffDatasets,
80
80
  diffGraphs: () => diffGraphs,
81
+ getScoresForTestSummaries: () => getScoresForTestSummaries,
81
82
  predicateMessage: () => predicateMessage,
82
83
  runSuite: () => runSuite,
83
84
  scenarioMessage: () => scenarioMessage,
@@ -1986,25 +1987,17 @@ function diffDatasets(datasetL, datasetR) {
1986
1987
  for (const t of times) {
1987
1988
  const valueL = datasetL.get(t);
1988
1989
  if (valueL !== void 0) {
1989
- if (valueL < minValueL)
1990
- minValueL = valueL;
1991
- if (valueL > maxValueL)
1992
- maxValueL = valueL;
1993
- if (valueL < minValue)
1994
- minValue = valueL;
1995
- if (valueL > maxValue)
1996
- maxValue = valueL;
1990
+ if (valueL < minValueL) minValueL = valueL;
1991
+ if (valueL > maxValueL) maxValueL = valueL;
1992
+ if (valueL < minValue) minValue = valueL;
1993
+ if (valueL > maxValue) maxValue = valueL;
1997
1994
  }
1998
1995
  const valueR = datasetR.get(t);
1999
1996
  if (valueR !== void 0) {
2000
- if (valueR < minValueR)
2001
- minValueR = valueR;
2002
- if (valueR > maxValueR)
2003
- maxValueR = valueR;
2004
- if (valueR < minValue)
2005
- minValue = valueR;
2006
- if (valueR > maxValue)
2007
- maxValue = valueR;
1997
+ if (valueR < minValueR) minValueR = valueR;
1998
+ if (valueR > maxValueR) maxValueR = valueR;
1999
+ if (valueR < minValue) minValue = valueR;
2000
+ if (valueR > maxValue) maxValue = valueR;
2008
2001
  }
2009
2002
  if (valueL === void 0 || valueR === void 0) {
2010
2003
  continue;
@@ -2163,9 +2156,6 @@ function restoreFromTerseSummaries(comparisonConfig, terseSummaries) {
2163
2156
  return allTestSummaries;
2164
2157
  }
2165
2158
 
2166
- // src/comparison/report/comparison-grouping.ts
2167
- var import_assert_never8 = require("assert-never");
2168
-
2169
2159
  // src/comparison/report/buckets.ts
2170
2160
  function getBucketIndex(diffPct, thresholds) {
2171
2161
  if (diffPct === 0) {
@@ -2179,7 +2169,33 @@ function getBucketIndex(diffPct, thresholds) {
2179
2169
  return thresholds.length + 1;
2180
2170
  }
2181
2171
 
2172
+ // src/comparison/report/comparison-group-scores.ts
2173
+ function getScoresForTestSummaries(testSummaries, thresholds) {
2174
+ const diffCountByBucket = Array(thresholds.length + 2).fill(0);
2175
+ const totalMaxDiffByBucket = Array(thresholds.length + 2).fill(0);
2176
+ let totalDiffCount = 0;
2177
+ for (const testSummary of testSummaries) {
2178
+ const bucketIndex = getBucketIndex(testSummary.md, thresholds);
2179
+ diffCountByBucket[bucketIndex]++;
2180
+ totalMaxDiffByBucket[bucketIndex] += testSummary.md;
2181
+ totalDiffCount++;
2182
+ }
2183
+ let diffPercentByBucket;
2184
+ if (totalDiffCount > 0) {
2185
+ diffPercentByBucket = diffCountByBucket.map((count) => count / totalDiffCount * 100);
2186
+ } else {
2187
+ diffPercentByBucket = [];
2188
+ }
2189
+ return {
2190
+ totalDiffCount,
2191
+ totalMaxDiffByBucket,
2192
+ diffCountByBucket,
2193
+ diffPercentByBucket
2194
+ };
2195
+ }
2196
+
2182
2197
  // src/comparison/report/comparison-grouping.ts
2198
+ var import_assert_never8 = require("assert-never");
2183
2199
  function categorizeComparisonTestSummaries(comparisonConfig, terseSummaries) {
2184
2200
  const allTestSummaries = restoreFromTerseSummaries(comparisonConfig, terseSummaries);
2185
2201
  const groupsByScenario = groupComparisonTestSummaries(allTestSummaries, "by-scenario");
@@ -2187,6 +2203,7 @@ function categorizeComparisonTestSummaries(comparisonConfig, terseSummaries) {
2187
2203
  const groupsByDataset = groupComparisonTestSummaries(allTestSummaries, "by-dataset");
2188
2204
  const byDataset = categorizeComparisonGroups(comparisonConfig, [...groupsByDataset.values()]);
2189
2205
  return {
2206
+ allTestSummaries,
2190
2207
  byScenario,
2191
2208
  byDataset
2192
2209
  };
@@ -2228,7 +2245,7 @@ function categorizeComparisonGroups(comparisonConfig, allGroups) {
2228
2245
  function addSummaryForGroup(group, root, validInL, validInR) {
2229
2246
  let scores;
2230
2247
  if (validInL && validInR) {
2231
- scores = getScoresForGroup(group, comparisonConfig.thresholds);
2248
+ scores = getScoresForTestSummaries(group.testSummaries, comparisonConfig.thresholds);
2232
2249
  }
2233
2250
  const groupSummary = {
2234
2251
  root,
@@ -2286,29 +2303,6 @@ function categorizeComparisonGroups(comparisonConfig, allGroups) {
2286
2303
  withoutDiffs
2287
2304
  };
2288
2305
  }
2289
- function getScoresForGroup(group, thresholds) {
2290
- const diffCountByBucket = Array(thresholds.length + 2).fill(0);
2291
- const totalMaxDiffByBucket = Array(thresholds.length + 2).fill(0);
2292
- let totalDiffCount = 0;
2293
- for (const testSummary of group.testSummaries) {
2294
- const bucketIndex = getBucketIndex(testSummary.md, thresholds);
2295
- diffCountByBucket[bucketIndex]++;
2296
- totalMaxDiffByBucket[bucketIndex] += testSummary.md;
2297
- totalDiffCount++;
2298
- }
2299
- let diffPercentByBucket;
2300
- if (totalDiffCount > 0) {
2301
- diffPercentByBucket = diffCountByBucket.map((count) => count / totalDiffCount * 100);
2302
- } else {
2303
- diffPercentByBucket = [];
2304
- }
2305
- return {
2306
- totalDiffCount,
2307
- totalMaxDiffByBucket,
2308
- diffCountByBucket,
2309
- diffPercentByBucket
2310
- };
2311
- }
2312
2306
  function sortDatasetGroupSummaries(summaries) {
2313
2307
  return summaries.sort((a, b) => {
2314
2308
  var _a, _b;
@@ -2368,57 +2362,6 @@ function compareScores(a, b) {
2368
2362
  return 0;
2369
2363
  }
2370
2364
 
2371
- // src/bundle/model-inputs.ts
2372
- var ModelInputs = class {
2373
- constructor(modelSpec) {
2374
- /** All inputs keyed by lookup name (lowercase variable name or alias). */
2375
- this.inputsByLookupName = /* @__PURE__ */ new Map();
2376
- /** All input ID aliases. */
2377
- this.inputIdAliases = /* @__PURE__ */ new Set();
2378
- for (const inputVar of modelSpec.inputVars.values()) {
2379
- const varNameKey = inputVar.varName.toLowerCase();
2380
- this.inputsByLookupName.set(varNameKey, inputVar);
2381
- if (inputVar.inputId) {
2382
- const idAlias = `id ${inputVar.inputId}`;
2383
- this.inputIdAliases.add(idAlias);
2384
- const idKey = idAlias.toLowerCase();
2385
- this.inputsByLookupName.set(idKey, inputVar);
2386
- }
2387
- }
2388
- if (modelSpec.inputAliases) {
2389
- for (const [alias, varId] of modelSpec.inputAliases.entries()) {
2390
- const aliasKey = alias.toLowerCase();
2391
- if (this.inputsByLookupName.has(aliasKey)) {
2392
- console.warn(
2393
- `WARNING: Input variable already defined with a name that collides with alias '${alias}', skipping`
2394
- );
2395
- continue;
2396
- }
2397
- const inputVar = modelSpec.inputVars.get(varId);
2398
- if (inputVar === void 0) {
2399
- console.warn(`WARNING: No input variable found for '${varId}' associated with alias '${alias}', skipping`);
2400
- continue;
2401
- }
2402
- this.inputsByLookupName.set(aliasKey, inputVar);
2403
- }
2404
- }
2405
- }
2406
- /**
2407
- * Return the set of `id XYZ` aliases for all input variables.
2408
- */
2409
- getAllInputIdAliases() {
2410
- return [...this.inputIdAliases];
2411
- }
2412
- /**
2413
- * Return the `InputVar` that matches the requested name (either by variable name or alias).
2414
- *
2415
- * @param name The variable name or alias to match.
2416
- */
2417
- getInputVarForName(name) {
2418
- return this.inputsByLookupName.get(name.toLowerCase());
2419
- }
2420
- };
2421
-
2422
2365
  // src/comparison/config/parse/comparison-parser.ts
2423
2366
  var import_ajv2 = __toESM(require("ajv"), 1);
2424
2367
  var import_assert_never9 = __toESM(require("assert-never"), 1);
@@ -2442,10 +2385,27 @@ var comparison_schema_default = {
2442
2385
  oneOf: [
2443
2386
  { $ref: "#/$defs/scenario_array_item" },
2444
2387
  { $ref: "#/$defs/scenario_group_array_item" },
2388
+ { $ref: "#/$defs/graph_group_array_item" },
2445
2389
  { $ref: "#/$defs/view_group_array_item" }
2446
2390
  ]
2447
2391
  },
2448
2392
  //
2393
+ // DATASETS
2394
+ //
2395
+ dataset: {
2396
+ type: "object",
2397
+ additionalProperties: false,
2398
+ properties: {
2399
+ name: {
2400
+ type: "string"
2401
+ },
2402
+ source: {
2403
+ type: "string"
2404
+ }
2405
+ },
2406
+ required: ["name"]
2407
+ },
2408
+ //
2449
2409
  // SCENARIOS
2450
2410
  //
2451
2411
  scenario_array_item: {
@@ -2685,9 +2645,62 @@ var comparison_schema_default = {
2685
2645
  required: ["scenario_group_ref"]
2686
2646
  },
2687
2647
  //
2648
+ // GRAPHS
2649
+ //
2650
+ graphs_preset: {
2651
+ type: "string",
2652
+ enum: ["all"]
2653
+ },
2654
+ graphs_array: {
2655
+ type: "array",
2656
+ items: {
2657
+ type: "string"
2658
+ },
2659
+ minItems: 1
2660
+ },
2661
+ graph_group_ref: {
2662
+ type: "object",
2663
+ additionalProperties: false,
2664
+ properties: {
2665
+ graph_group_ref: {
2666
+ type: "string"
2667
+ }
2668
+ },
2669
+ required: ["graph_group_ref"]
2670
+ },
2671
+ //
2672
+ // GRAPH GROUPS
2673
+ //
2674
+ graph_group_array_item: {
2675
+ type: "object",
2676
+ additionalProperties: false,
2677
+ properties: {
2678
+ graph_group: {
2679
+ $ref: "#/$defs/graph_group"
2680
+ }
2681
+ },
2682
+ required: ["graph_group"]
2683
+ },
2684
+ graph_group: {
2685
+ type: "object",
2686
+ additionalProperties: false,
2687
+ properties: {
2688
+ id: {
2689
+ type: "string"
2690
+ },
2691
+ graphs: {
2692
+ $ref: "#/$defs/graphs_array"
2693
+ }
2694
+ },
2695
+ required: ["id", "graphs"]
2696
+ },
2697
+ //
2688
2698
  // VIEWS
2689
2699
  //
2690
2700
  view: {
2701
+ oneOf: [{ $ref: "#/$defs/view_with_scenario" }, { $ref: "#/$defs/view_with_rows" }]
2702
+ },
2703
+ view_with_scenario: {
2691
2704
  type: "object",
2692
2705
  additionalProperties: false,
2693
2706
  properties: {
@@ -2702,24 +2715,103 @@ var comparison_schema_default = {
2702
2715
  },
2703
2716
  graphs: {
2704
2717
  $ref: "#/$defs/view_graphs"
2718
+ },
2719
+ graph_order: {
2720
+ $ref: "#/$defs/view_graph_order"
2705
2721
  }
2706
2722
  },
2707
- required: ["scenario_ref", "graphs"]
2723
+ required: ["scenario_ref"]
2708
2724
  },
2709
- view_graphs: {
2710
- oneOf: [{ $ref: "#/$defs/view_graphs_preset" }, { $ref: "#/$defs/view_graphs_array" }]
2725
+ view_with_rows: {
2726
+ type: "object",
2727
+ additionalProperties: false,
2728
+ properties: {
2729
+ title: {
2730
+ type: "string"
2731
+ },
2732
+ subtitle: {
2733
+ type: "string"
2734
+ },
2735
+ rows: {
2736
+ $ref: "#/$defs/view_rows_array"
2737
+ }
2738
+ },
2739
+ required: ["title", "rows"]
2711
2740
  },
2712
- view_graphs_preset: {
2713
- type: "string",
2714
- enum: ["all"]
2741
+ view_rows_array: {
2742
+ type: "array",
2743
+ items: {
2744
+ $ref: "#/$defs/view_rows_array_item"
2745
+ },
2746
+ minItems: 1
2747
+ },
2748
+ view_rows_array_item: {
2749
+ type: "object",
2750
+ additionalProperties: false,
2751
+ properties: {
2752
+ row: {
2753
+ $ref: "#/$defs/view_row"
2754
+ }
2755
+ }
2756
+ },
2757
+ view_row: {
2758
+ type: "object",
2759
+ additionalProperties: false,
2760
+ properties: {
2761
+ title: {
2762
+ type: "string"
2763
+ },
2764
+ subtitle: {
2765
+ type: "string"
2766
+ },
2767
+ boxes: {
2768
+ $ref: "#/$defs/view_boxes_array"
2769
+ }
2770
+ },
2771
+ required: ["title", "boxes"]
2715
2772
  },
2716
- view_graphs_array: {
2773
+ view_boxes_array: {
2717
2774
  type: "array",
2718
2775
  items: {
2719
- type: "string"
2776
+ $ref: "#/$defs/view_boxes_array_item"
2720
2777
  },
2721
2778
  minItems: 1
2722
2779
  },
2780
+ view_boxes_array_item: {
2781
+ type: "object",
2782
+ additionalProperties: false,
2783
+ properties: {
2784
+ box: {
2785
+ $ref: "#/$defs/view_box"
2786
+ }
2787
+ }
2788
+ },
2789
+ view_box: {
2790
+ type: "object",
2791
+ additionalProperties: false,
2792
+ properties: {
2793
+ title: {
2794
+ type: "string"
2795
+ },
2796
+ subtitle: {
2797
+ type: "string"
2798
+ },
2799
+ dataset: {
2800
+ $ref: "#/$defs/dataset"
2801
+ },
2802
+ scenario_ref: {
2803
+ $ref: "#/$defs/scenario_ref_id"
2804
+ }
2805
+ },
2806
+ required: ["title", "dataset", "scenario_ref"]
2807
+ },
2808
+ view_graphs: {
2809
+ oneOf: [{ $ref: "#/$defs/graphs_preset" }, { $ref: "#/$defs/graphs_array" }, { $ref: "#/$defs/graph_group_ref" }]
2810
+ },
2811
+ view_graph_order: {
2812
+ type: "string",
2813
+ enum: ["default", "grouped-by-diffs"]
2814
+ },
2723
2815
  //
2724
2816
  // VIEW GROUPS
2725
2817
  //
@@ -2795,6 +2887,7 @@ var comparison_schema_default = {
2795
2887
  function parseComparisonSpecs(specSource) {
2796
2888
  const scenarios = [];
2797
2889
  const scenarioGroups = [];
2890
+ const graphGroups = [];
2798
2891
  const viewGroups = [];
2799
2892
  const ajv = new import_ajv2.default();
2800
2893
  const validate = ajv.compile(comparison_schema_default);
@@ -2815,6 +2908,8 @@ function parseComparisonSpecs(specSource) {
2815
2908
  scenarios.push(scenarioSpecFromParsed(specItem.scenario));
2816
2909
  } else if ("scenario_group" in specItem) {
2817
2910
  scenarioGroups.push(scenarioGroupSpecFromParsed(specItem.scenario_group));
2911
+ } else if ("graph_group" in specItem) {
2912
+ graphGroups.push(graphGroupSpecFromParsed(specItem.graph_group));
2818
2913
  } else if ("view_group" in specItem) {
2819
2914
  viewGroups.push(viewGroupSpecFromParsed(specItem.view_group));
2820
2915
  }
@@ -2832,9 +2927,17 @@ ${error.message}`;
2832
2927
  return (0, import_neverthrow2.ok)({
2833
2928
  scenarios,
2834
2929
  scenarioGroups,
2930
+ graphGroups,
2835
2931
  viewGroups
2836
2932
  });
2837
2933
  }
2934
+ function datasetSpecFromParsed(parsedDataset) {
2935
+ return {
2936
+ kind: "dataset",
2937
+ name: parsedDataset.name,
2938
+ source: parsedDataset.source
2939
+ };
2940
+ }
2838
2941
  function scenarioSpecFromParsed(parsedScenario) {
2839
2942
  if (parsedScenario.preset === "matrix") {
2840
2943
  return {
@@ -2927,13 +3030,50 @@ function scenarioGroupRefSpecFromParsed(parsedGroupRef) {
2927
3030
  groupId: parsedGroupRef.scenario_group_ref
2928
3031
  };
2929
3032
  }
3033
+ function graphGroupSpecFromParsed(parsedGraphGroup) {
3034
+ return {
3035
+ kind: "graph-group",
3036
+ id: parsedGraphGroup.id,
3037
+ graphIds: parsedGraphGroup.graphs
3038
+ };
3039
+ }
2930
3040
  function viewSpecFromParsed(parsedView) {
3041
+ let scenarioId;
3042
+ let rows;
3043
+ if (parsedView.scenario_ref !== void 0) {
3044
+ scenarioId = parsedView.scenario_ref;
3045
+ } else if (parsedView.rows !== void 0) {
3046
+ rows = parsedView.rows.map((item) => viewRowSpecFromParsed(item.row));
3047
+ }
3048
+ let graphs;
3049
+ if (parsedView.graphs !== void 0) {
3050
+ graphs = viewGraphsSpecFromParsed(parsedView.graphs);
3051
+ }
2931
3052
  return {
2932
3053
  kind: "view",
2933
3054
  title: parsedView.title,
2934
3055
  subtitle: parsedView.subtitle,
2935
- scenarioId: parsedView.scenario_ref,
2936
- graphs: viewGraphsSpecFromParsed(parsedView.graphs)
3056
+ scenarioId,
3057
+ rows,
3058
+ graphs,
3059
+ graphOrder: parsedView.graph_order
3060
+ };
3061
+ }
3062
+ function viewRowSpecFromParsed(parsedRow) {
3063
+ return {
3064
+ kind: "view-row",
3065
+ title: parsedRow.title,
3066
+ subtitle: parsedRow.subtitle,
3067
+ boxes: parsedRow.boxes.map((item) => viewBoxSpecFromParsed(item.box))
3068
+ };
3069
+ }
3070
+ function viewBoxSpecFromParsed(parsedBox) {
3071
+ return {
3072
+ kind: "view-box",
3073
+ title: parsedBox.title,
3074
+ subtitle: parsedBox.subtitle,
3075
+ dataset: datasetSpecFromParsed(parsedBox.dataset),
3076
+ scenarioId: parsedBox.scenario_ref
2937
3077
  };
2938
3078
  }
2939
3079
  function viewGraphsSpecFromParsed(parsedGraphs) {
@@ -2942,11 +3082,18 @@ function viewGraphsSpecFromParsed(parsedGraphs) {
2942
3082
  kind: "graphs-preset",
2943
3083
  preset: "all"
2944
3084
  };
2945
- } else {
3085
+ } else if (Array.isArray(parsedGraphs)) {
2946
3086
  return {
2947
3087
  kind: "graphs-array",
2948
3088
  graphIds: parsedGraphs
2949
3089
  };
3090
+ } else if ("graph_group_ref" in parsedGraphs) {
3091
+ return {
3092
+ kind: "graph-group-ref",
3093
+ groupId: parsedGraphs.graph_group_ref
3094
+ };
3095
+ } else {
3096
+ throw new Error("Invalid graphs spec in comparison view");
2950
3097
  }
2951
3098
  }
2952
3099
  function viewGroupSpecFromParsed(parsedViewGroup) {
@@ -2982,6 +3129,57 @@ function viewGroupSpecFromParsed(parsedViewGroup) {
2982
3129
  // src/comparison/config/resolve/comparison-resolver.ts
2983
3130
  var import_assert_never11 = require("assert-never");
2984
3131
 
3132
+ // src/bundle/model-inputs.ts
3133
+ var ModelInputs = class {
3134
+ constructor(modelSpec) {
3135
+ /** All inputs keyed by lookup name (lowercase variable name or alias). */
3136
+ this.inputsByLookupName = /* @__PURE__ */ new Map();
3137
+ /** All input ID aliases. */
3138
+ this.inputIdAliases = /* @__PURE__ */ new Set();
3139
+ for (const inputVar of modelSpec.inputVars.values()) {
3140
+ const varNameKey = inputVar.varName.toLowerCase();
3141
+ this.inputsByLookupName.set(varNameKey, inputVar);
3142
+ if (inputVar.inputId) {
3143
+ const idAlias = `id ${inputVar.inputId}`;
3144
+ this.inputIdAliases.add(idAlias);
3145
+ const idKey = idAlias.toLowerCase();
3146
+ this.inputsByLookupName.set(idKey, inputVar);
3147
+ }
3148
+ }
3149
+ if (modelSpec.inputAliases) {
3150
+ for (const [alias, varId] of modelSpec.inputAliases.entries()) {
3151
+ const aliasKey = alias.toLowerCase();
3152
+ if (this.inputsByLookupName.has(aliasKey)) {
3153
+ console.warn(
3154
+ `WARNING: Input variable already defined with a name that collides with alias '${alias}', skipping`
3155
+ );
3156
+ continue;
3157
+ }
3158
+ const inputVar = modelSpec.inputVars.get(varId);
3159
+ if (inputVar === void 0) {
3160
+ console.warn(`WARNING: No input variable found for '${varId}' associated with alias '${alias}', skipping`);
3161
+ continue;
3162
+ }
3163
+ this.inputsByLookupName.set(aliasKey, inputVar);
3164
+ }
3165
+ }
3166
+ }
3167
+ /**
3168
+ * Return the set of `id XYZ` aliases for all input variables.
3169
+ */
3170
+ getAllInputIdAliases() {
3171
+ return [...this.inputIdAliases];
3172
+ }
3173
+ /**
3174
+ * Return the `InputVar` that matches the requested name (either by variable name or alias).
3175
+ *
3176
+ * @param name The variable name or alias to match.
3177
+ */
3178
+ getInputVarForName(name) {
3179
+ return this.inputsByLookupName.get(name.toLowerCase());
3180
+ }
3181
+ };
3182
+
2985
3183
  // src/comparison/config/resolve/comparison-scenario-specs.ts
2986
3184
  var import_assert_never10 = require("assert-never");
2987
3185
  function scenarioSpecsFromSettings(settings) {
@@ -3020,17 +3218,20 @@ function inputSettingFromResolvedInputState(state) {
3020
3218
  }
3021
3219
 
3022
3220
  // src/comparison/config/resolve/comparison-resolver.ts
3023
- function resolveComparisonSpecs(modelInputsL, modelInputsR, specs) {
3221
+ function resolveComparisonSpecs(modelSpecL, modelSpecR, specs) {
3024
3222
  let key = 1;
3025
3223
  const genKey = () => {
3026
3224
  return `${key++}`;
3027
3225
  };
3226
+ const modelInputsL = new ModelInputs(modelSpecL);
3227
+ const modelInputsR = new ModelInputs(modelSpecR);
3228
+ const modelOutputs = new ModelOutputs(modelSpecL, modelSpecR);
3028
3229
  const resolvedScenarios = new ResolvedScenarios();
3029
- for (const scenarioSpec of specs.scenarios) {
3230
+ for (const scenarioSpec of specs.scenarios || []) {
3030
3231
  resolvedScenarios.add(resolveScenariosFromSpec(modelInputsL, modelInputsR, scenarioSpec, genKey));
3031
3232
  }
3032
3233
  const partiallyResolvedScenarioGroups = [];
3033
- for (const scenarioGroupSpec of specs.scenarioGroups) {
3234
+ for (const scenarioGroupSpec of specs.scenarioGroups || []) {
3034
3235
  const scenariosForGroup = [];
3035
3236
  for (const scenarioItem of scenarioGroupSpec.scenarios) {
3036
3237
  if (scenarioItem.kind === "scenario-ref") {
@@ -3078,9 +3279,21 @@ function resolveComparisonSpecs(modelInputsL, modelInputsR, specs) {
3078
3279
  scenarios: scenariosForGroup
3079
3280
  });
3080
3281
  }
3282
+ const resolvedGraphGroups = new ResolvedGraphGroups();
3283
+ for (const graphGroupSpec of specs.graphGroups || []) {
3284
+ resolvedGraphGroups.add(graphGroupSpec);
3285
+ }
3081
3286
  const resolvedViewGroups = [];
3082
- for (const viewGroupSpec of specs.viewGroups) {
3083
- const resolvedViewGroup = resolveViewGroupFromSpec(resolvedScenarios, resolvedScenarioGroups, viewGroupSpec);
3287
+ for (const viewGroupSpec of specs.viewGroups || []) {
3288
+ const resolvedViewGroup = resolveViewGroupFromSpec(
3289
+ modelSpecL,
3290
+ modelSpecR,
3291
+ modelOutputs,
3292
+ resolvedScenarios,
3293
+ resolvedScenarioGroups,
3294
+ resolvedGraphGroups,
3295
+ viewGroupSpec
3296
+ );
3084
3297
  resolvedViewGroups.push(resolvedViewGroup);
3085
3298
  }
3086
3299
  return {
@@ -3089,6 +3302,35 @@ function resolveComparisonSpecs(modelInputsL, modelInputsR, specs) {
3089
3302
  viewGroups: resolvedViewGroups
3090
3303
  };
3091
3304
  }
3305
+ var ModelOutputs = class {
3306
+ constructor(modelSpecL, modelSpecR) {
3307
+ this.modelSpecL = modelSpecL;
3308
+ this.modelSpecR = modelSpecR;
3309
+ }
3310
+ getDatasetForName(name, source) {
3311
+ function findOutputVar(modelSpec) {
3312
+ for (const outputVar of modelSpec.outputVars.values()) {
3313
+ if (outputVar.varName === name && outputVar.sourceName === source) {
3314
+ return outputVar;
3315
+ }
3316
+ }
3317
+ return void 0;
3318
+ }
3319
+ const outputVarR = findOutputVar(this.modelSpecR);
3320
+ if (outputVarR) {
3321
+ const datasetKey = outputVarR.datasetKey;
3322
+ const outputVarL = this.modelSpecL.outputVars.get(datasetKey);
3323
+ return {
3324
+ kind: "dataset",
3325
+ key: datasetKey,
3326
+ outputVarL,
3327
+ outputVarR
3328
+ };
3329
+ } else {
3330
+ return void 0;
3331
+ }
3332
+ }
3333
+ };
3092
3334
  var ResolvedScenarios = class {
3093
3335
  constructor() {
3094
3336
  /** The array of all resolved scenarios. */
@@ -3382,33 +3624,73 @@ var ResolvedScenarioGroups = class {
3382
3624
  return this.resolvedGroupsById.get(id);
3383
3625
  }
3384
3626
  };
3385
- function resolveGraphsFromSpec(graphsSpec) {
3627
+ var ResolvedGraphGroups = class {
3628
+ constructor() {
3629
+ /** The set of resolved graph groups, keyed by ID. */
3630
+ this.resolvedGroupsById = /* @__PURE__ */ new Map();
3631
+ }
3632
+ add(group) {
3633
+ if (this.resolvedGroupsById.has(group.id)) {
3634
+ throw new Error(`Multiple graph groups defined with the same id (${group.id})`);
3635
+ }
3636
+ this.resolvedGroupsById.set(group.id, group);
3637
+ }
3638
+ getGroupForId(id) {
3639
+ return this.resolvedGroupsById.get(id);
3640
+ }
3641
+ };
3642
+ function resolveGraphsFromSpec(modelSpecL, modelSpecR, resolvedGraphGroups, graphsSpec) {
3386
3643
  switch (graphsSpec.kind) {
3387
- case "graphs-preset":
3388
- return "all";
3644
+ case "graphs-preset": {
3645
+ switch (graphsSpec.preset) {
3646
+ case "all": {
3647
+ const graphIds = /* @__PURE__ */ new Set();
3648
+ const addGraphIds = (modelSpec) => {
3649
+ if (modelSpec.graphSpecs) {
3650
+ for (const graphSpec of modelSpec.graphSpecs) {
3651
+ graphIds.add(graphSpec.id);
3652
+ }
3653
+ }
3654
+ };
3655
+ addGraphIds(modelSpecL);
3656
+ addGraphIds(modelSpecR);
3657
+ return [...graphIds];
3658
+ }
3659
+ default:
3660
+ (0, import_assert_never11.assertNever)(graphsSpec.preset);
3661
+ }
3662
+ }
3663
+ // eslint-disable-next-line no-fallthrough
3389
3664
  case "graphs-array":
3390
3665
  return graphsSpec.graphIds;
3666
+ case "graph-group-ref": {
3667
+ const groupSpec = resolvedGraphGroups.getGroupForId(graphsSpec.groupId);
3668
+ if (groupSpec === void 0) {
3669
+ throw new Error(`No graph group found for id ${graphsSpec.groupId}`);
3670
+ }
3671
+ return groupSpec.graphIds;
3672
+ }
3391
3673
  default:
3392
3674
  (0, import_assert_never11.assertNever)(graphsSpec);
3393
3675
  }
3394
3676
  }
3395
- function resolveViewForScenarioId(resolvedScenarios, viewTitle, viewSubtitle, scenarioId, graphs) {
3677
+ function resolveViewForScenarioId(resolvedScenarios, viewTitle, viewSubtitle, scenarioId, graphIds, graphOrder) {
3396
3678
  const resolvedScenario = resolvedScenarios.getScenarioForId(scenarioId);
3397
3679
  if (resolvedScenario) {
3398
- return resolveViewForScenario(viewTitle, viewSubtitle, resolvedScenario, graphs);
3680
+ return resolveViewForScenario(viewTitle, viewSubtitle, resolvedScenario, graphIds, graphOrder);
3399
3681
  } else {
3400
3682
  return unresolvedViewForScenarioId(viewTitle, viewSubtitle, scenarioId);
3401
3683
  }
3402
3684
  }
3403
- function resolveViewForScenarioRefSpec(resolvedScenarios, refSpec, graphs) {
3685
+ function resolveViewForScenarioRefSpec(resolvedScenarios, refSpec, graphIds, graphOrder) {
3404
3686
  const resolvedScenario = resolvedScenarios.getScenarioForId(refSpec.scenarioId);
3405
3687
  if (resolvedScenario) {
3406
- return resolveViewForScenario(refSpec.title, refSpec.subtitle, resolvedScenario, graphs);
3688
+ return resolveViewForScenario(refSpec.title, refSpec.subtitle, resolvedScenario, graphIds, graphOrder);
3407
3689
  } else {
3408
3690
  return unresolvedViewForScenarioId(void 0, void 0, refSpec.scenarioId);
3409
3691
  }
3410
3692
  }
3411
- function resolveViewForScenario(viewTitle, viewSubtitle, resolvedScenario, graphs) {
3693
+ function resolveViewForScenario(viewTitle, viewSubtitle, resolvedScenario, graphIds, graphOrder) {
3412
3694
  if (viewTitle === void 0) {
3413
3695
  viewTitle = resolvedScenario.title;
3414
3696
  if (viewTitle === void 0) {
@@ -3423,15 +3705,83 @@ function resolveViewForScenario(viewTitle, viewSubtitle, resolvedScenario, graph
3423
3705
  title: viewTitle,
3424
3706
  subtitle: viewSubtitle,
3425
3707
  scenario: resolvedScenario,
3426
- graphs
3708
+ graphIds,
3709
+ graphOrder
3427
3710
  };
3428
3711
  }
3429
- function unresolvedViewForScenarioId(viewTitle, viewSubtitle, scenarioId) {
3712
+ function resolveViewBoxForSpec(modelOutputs, resolvedScenarios, boxSpec) {
3713
+ const resolvedDataset = modelOutputs.getDatasetForName(boxSpec.dataset.name, boxSpec.dataset.source);
3714
+ if (resolvedDataset === void 0) {
3715
+ return {
3716
+ kind: "unresolved-view-box",
3717
+ datasetName: boxSpec.dataset.name,
3718
+ datasetSource: boxSpec.dataset.source
3719
+ };
3720
+ }
3721
+ const resolvedScenario = resolvedScenarios.getScenarioForId(boxSpec.scenarioId);
3722
+ if (resolvedScenario === void 0) {
3723
+ return {
3724
+ kind: "unresolved-view-box",
3725
+ scenarioId: boxSpec.scenarioId
3726
+ };
3727
+ }
3728
+ return {
3729
+ kind: "view-box",
3730
+ title: boxSpec.title,
3731
+ subtitle: boxSpec.subtitle,
3732
+ dataset: resolvedDataset,
3733
+ scenario: resolvedScenario
3734
+ };
3735
+ }
3736
+ function resolveViewRowForSpec(modelOutputs, resolvedScenarios, rowSpec) {
3737
+ const resolvedBoxes = [];
3738
+ for (const boxSpec of rowSpec.boxes) {
3739
+ const box = resolveViewBoxForSpec(modelOutputs, resolvedScenarios, boxSpec);
3740
+ if (box.kind === "view-box") {
3741
+ resolvedBoxes.push(box);
3742
+ } else {
3743
+ return box;
3744
+ }
3745
+ }
3746
+ return {
3747
+ kind: "view-row",
3748
+ title: rowSpec.title,
3749
+ subtitle: rowSpec.subtitle,
3750
+ boxes: resolvedBoxes
3751
+ };
3752
+ }
3753
+ function resolveViewWithRowSpecs(modelOutputs, resolvedScenarios, viewTitle, viewSubtitle, rowSpecs) {
3754
+ const resolvedRows = [];
3755
+ for (const rowSpec of rowSpecs) {
3756
+ const row = resolveViewRowForSpec(modelOutputs, resolvedScenarios, rowSpec);
3757
+ if (row.kind === "view-row") {
3758
+ resolvedRows.push(row);
3759
+ } else {
3760
+ return unresolvedViewForScenarioId(viewTitle, viewSubtitle, row.scenarioId, row.datasetName, row.datasetSource);
3761
+ }
3762
+ }
3763
+ if (viewTitle === void 0) {
3764
+ viewTitle = "Untitled view";
3765
+ }
3766
+ return {
3767
+ kind: "view",
3768
+ title: viewTitle,
3769
+ subtitle: viewSubtitle,
3770
+ rows: resolvedRows,
3771
+ // TODO: The schema doesn't currently allow for graphs in a view that is defined
3772
+ // with rows. Probably we should have different view types to make this more clear.
3773
+ graphIds: [],
3774
+ graphOrder: "default"
3775
+ };
3776
+ }
3777
+ function unresolvedViewForScenarioId(viewTitle, viewSubtitle, scenarioId, datasetName, datasetSource) {
3430
3778
  return {
3431
3779
  kind: "unresolved-view",
3432
3780
  title: viewTitle,
3433
3781
  subtitle: viewSubtitle,
3434
- scenarioId
3782
+ scenarioId,
3783
+ datasetName,
3784
+ datasetSource
3435
3785
  };
3436
3786
  }
3437
3787
  function unresolvedViewForScenarioGroupId(viewTitle, viewSubtitle, scenarioGroupId) {
@@ -3442,29 +3792,46 @@ function unresolvedViewForScenarioGroupId(viewTitle, viewSubtitle, scenarioGroup
3442
3792
  scenarioGroupId
3443
3793
  };
3444
3794
  }
3445
- function resolveViewGroupFromSpec(resolvedScenarios, resolvedScenarioGroups, viewGroupSpec) {
3795
+ function resolveViewGroupFromSpec(modelSpecL, modelSpecR, modelOutputs, resolvedScenarios, resolvedScenarioGroups, resolvedGraphGroups, viewGroupSpec) {
3446
3796
  let views;
3447
3797
  switch (viewGroupSpec.kind) {
3448
3798
  case "view-group-with-views": {
3449
3799
  views = viewGroupSpec.views.map((viewSpec) => {
3450
- const graphs = resolveGraphsFromSpec(viewSpec.graphs);
3451
- return resolveViewForScenarioId(
3452
- resolvedScenarios,
3453
- viewSpec.title,
3454
- viewSpec.subtitle,
3455
- viewSpec.scenarioId,
3456
- graphs
3457
- );
3800
+ if (viewSpec.scenarioId) {
3801
+ let graphIds;
3802
+ if (viewSpec.graphs) {
3803
+ graphIds = resolveGraphsFromSpec(modelSpecL, modelSpecR, resolvedGraphGroups, viewSpec.graphs);
3804
+ } else {
3805
+ graphIds = [];
3806
+ }
3807
+ return resolveViewForScenarioId(
3808
+ resolvedScenarios,
3809
+ viewSpec.title,
3810
+ viewSpec.subtitle,
3811
+ viewSpec.scenarioId,
3812
+ graphIds,
3813
+ viewSpec.graphOrder || "default"
3814
+ );
3815
+ } else {
3816
+ return resolveViewWithRowSpecs(
3817
+ modelOutputs,
3818
+ resolvedScenarios,
3819
+ viewSpec.title,
3820
+ viewSpec.subtitle,
3821
+ viewSpec.rows
3822
+ );
3823
+ }
3458
3824
  });
3459
3825
  break;
3460
3826
  }
3461
3827
  case "view-group-with-scenarios": {
3462
- const graphs = resolveGraphsFromSpec(viewGroupSpec.graphs);
3828
+ const graphIds = resolveGraphsFromSpec(modelSpecL, modelSpecR, resolvedGraphGroups, viewGroupSpec.graphs);
3829
+ const graphOrder = viewGroupSpec.graphOrder || "default";
3463
3830
  views = [];
3464
3831
  for (const refSpec of viewGroupSpec.scenarios) {
3465
3832
  switch (refSpec.kind) {
3466
3833
  case "scenario-ref":
3467
- views.push(resolveViewForScenarioRefSpec(resolvedScenarios, refSpec, graphs));
3834
+ views.push(resolveViewForScenarioRefSpec(resolvedScenarios, refSpec, graphIds, graphOrder));
3468
3835
  break;
3469
3836
  case "scenario-group-ref": {
3470
3837
  const resolvedGroup = resolvedScenarioGroups.getGroupForId(refSpec.groupId);
@@ -3475,7 +3842,7 @@ function resolveViewGroupFromSpec(resolvedScenarios, resolvedScenarioGroups, vie
3475
3842
  views.push(unresolvedViewForScenarioId(void 0, void 0, scenario.scenarioId));
3476
3843
  break;
3477
3844
  case "scenario":
3478
- views.push(resolveViewForScenario(void 0, void 0, scenario, graphs));
3845
+ views.push(resolveViewForScenario(void 0, void 0, scenario, graphIds, graphOrder));
3479
3846
  break;
3480
3847
  default:
3481
3848
  (0, import_assert_never11.assertNever)(scenario);
@@ -3503,10 +3870,11 @@ function resolveViewGroupFromSpec(resolvedScenarios, resolvedScenarioGroups, vie
3503
3870
  }
3504
3871
 
3505
3872
  // src/comparison/config/comparison-config.ts
3506
- function resolveComparisonSpecsFromSources(modelInputsL, modelInputsR, specSources) {
3873
+ function resolveComparisonSpecsFromSources(modelSpecL, modelSpecR, specSources) {
3507
3874
  const combinedSpecs = {
3508
3875
  scenarios: [],
3509
3876
  scenarioGroups: [],
3877
+ graphGroups: [],
3510
3878
  viewGroups: []
3511
3879
  };
3512
3880
  for (const specSource of specSources) {
@@ -3523,11 +3891,12 @@ function resolveComparisonSpecsFromSources(modelInputsL, modelInputsR, specSourc
3523
3891
  } else {
3524
3892
  specs = specSource;
3525
3893
  }
3526
- combinedSpecs.scenarios.push(...specs.scenarios);
3527
- combinedSpecs.scenarioGroups.push(...specs.scenarioGroups);
3528
- combinedSpecs.viewGroups.push(...specs.viewGroups);
3894
+ combinedSpecs.scenarios.push(...specs.scenarios || []);
3895
+ combinedSpecs.scenarioGroups.push(...specs.scenarioGroups || []);
3896
+ combinedSpecs.graphGroups.push(...specs.graphGroups || []);
3897
+ combinedSpecs.viewGroups.push(...specs.viewGroups || []);
3529
3898
  }
3530
- return resolveComparisonSpecs(modelInputsL, modelInputsR, combinedSpecs);
3899
+ return resolveComparisonSpecs(modelSpecL, modelSpecR, combinedSpecs);
3531
3900
  }
3532
3901
 
3533
3902
  // src/comparison/config/comparison-datasets.ts
@@ -3541,6 +3910,8 @@ var ComparisonDatasetsImpl = class {
3541
3910
  * @param datasetOptions The custom configuration for the datasets to be compared.
3542
3911
  */
3543
3912
  constructor(modelSpecL, modelSpecR, datasetOptions) {
3913
+ this.modelSpecL = modelSpecL;
3914
+ this.modelSpecR = modelSpecR;
3544
3915
  this.datasetOptions = datasetOptions;
3545
3916
  const renamedDatasetKeys = datasetOptions == null ? void 0 : datasetOptions.renamedDatasetKeys;
3546
3917
  const invertedRenamedKeys = /* @__PURE__ */ new Map();
@@ -3599,7 +3970,47 @@ var ComparisonDatasetsImpl = class {
3599
3970
  }
3600
3971
  }
3601
3972
  }
3973
+ // from ComparisonDatasets interface
3974
+ getReferencePlotsForDataset(datasetKey, scenario) {
3975
+ var _a;
3976
+ if (((_a = this.datasetOptions) == null ? void 0 : _a.referencePlotsForDataset) !== void 0) {
3977
+ const dataset = this.getDataset(datasetKey);
3978
+ if (dataset !== void 0) {
3979
+ return this.datasetOptions.referencePlotsForDataset(dataset, scenario);
3980
+ }
3981
+ }
3982
+ return [];
3983
+ }
3984
+ // from ComparisonDatasets interface
3985
+ getContextGraphIdsForDataset(datasetKey, scenario) {
3986
+ var _a;
3987
+ const dataset = this.getDataset(datasetKey);
3988
+ if (dataset === void 0) {
3989
+ return [];
3990
+ }
3991
+ if (((_a = this.datasetOptions) == null ? void 0 : _a.contextGraphIdsForDataset) !== void 0) {
3992
+ return this.datasetOptions.contextGraphIdsForDataset(dataset, scenario);
3993
+ } else {
3994
+ return getContextGraphIdsForDataset(this.modelSpecL, this.modelSpecR, dataset);
3995
+ }
3996
+ }
3602
3997
  };
3998
+ function getContextGraphIdsForDataset(modelSpecL, modelSpecR, dataset) {
3999
+ const contextGraphIds = /* @__PURE__ */ new Set();
4000
+ function addGraphs(modelSpec, outputVar) {
4001
+ for (const graphSpec of modelSpec.graphSpecs || []) {
4002
+ for (const graphDatasetSpec of graphSpec.datasets) {
4003
+ if (graphDatasetSpec.datasetKey === (outputVar == null ? void 0 : outputVar.datasetKey)) {
4004
+ contextGraphIds.add(graphSpec.id);
4005
+ break;
4006
+ }
4007
+ }
4008
+ }
4009
+ }
4010
+ addGraphs(modelSpecL, dataset.outputVarL);
4011
+ addGraphs(modelSpecR, dataset.outputVarR);
4012
+ return [...contextGraphIds];
4013
+ }
3603
4014
 
3604
4015
  // src/comparison/config/comparison-scenarios.ts
3605
4016
  function getComparisonScenarios(scenarios) {
@@ -3726,16 +4137,15 @@ function createConfig(options) {
3726
4137
  });
3727
4138
  const modelSpecL = baselineBundle.model.modelSpec;
3728
4139
  const modelSpecR = currentBundle.model.modelSpec;
3729
- const modelInputsL = new ModelInputs(modelSpecL);
3730
- const modelInputsR = new ModelInputs(modelSpecR);
3731
- const comparisonDefs = resolveComparisonSpecsFromSources(modelInputsL, modelInputsR, options.comparison.specs);
4140
+ const comparisonDefs = resolveComparisonSpecsFromSources(modelSpecL, modelSpecR, options.comparison.specs);
3732
4141
  comparisonConfig = {
3733
4142
  bundleL: baselineBundle,
3734
4143
  bundleR: currentBundle,
3735
4144
  thresholds: options.comparison.thresholds,
3736
4145
  scenarios: getComparisonScenarios(comparisonDefs.scenarios),
3737
4146
  datasets: getComparisonDatasets(modelSpecL, modelSpecR, options.comparison.datasets),
3738
- viewGroups: comparisonDefs.viewGroups
4147
+ viewGroups: comparisonDefs.viewGroups,
4148
+ reportOptions: options.comparison.report
3739
4149
  };
3740
4150
  }
3741
4151
  const checkConfig = {
@@ -4341,6 +4751,7 @@ function suiteSummaryFromReport(suiteReport) {
4341
4751
  datasetMessage,
4342
4752
  diffDatasets,
4343
4753
  diffGraphs,
4754
+ getScoresForTestSummaries,
4344
4755
  predicateMessage,
4345
4756
  runSuite,
4346
4757
  scenarioMessage,