@sdeverywhere/check-core 0.1.2 → 0.1.3

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.js CHANGED
@@ -1939,25 +1939,17 @@ function diffDatasets(datasetL, datasetR) {
1939
1939
  for (const t of times) {
1940
1940
  const valueL = datasetL.get(t);
1941
1941
  if (valueL !== void 0) {
1942
- if (valueL < minValueL)
1943
- minValueL = valueL;
1944
- if (valueL > maxValueL)
1945
- maxValueL = valueL;
1946
- if (valueL < minValue)
1947
- minValue = valueL;
1948
- if (valueL > maxValue)
1949
- maxValue = valueL;
1942
+ if (valueL < minValueL) minValueL = valueL;
1943
+ if (valueL > maxValueL) maxValueL = valueL;
1944
+ if (valueL < minValue) minValue = valueL;
1945
+ if (valueL > maxValue) maxValue = valueL;
1950
1946
  }
1951
1947
  const valueR = datasetR.get(t);
1952
1948
  if (valueR !== void 0) {
1953
- if (valueR < minValueR)
1954
- minValueR = valueR;
1955
- if (valueR > maxValueR)
1956
- maxValueR = valueR;
1957
- if (valueR < minValue)
1958
- minValue = valueR;
1959
- if (valueR > maxValue)
1960
- maxValue = valueR;
1949
+ if (valueR < minValueR) minValueR = valueR;
1950
+ if (valueR > maxValueR) maxValueR = valueR;
1951
+ if (valueR < minValue) minValue = valueR;
1952
+ if (valueR > maxValue) maxValue = valueR;
1961
1953
  }
1962
1954
  if (valueL === void 0 || valueR === void 0) {
1963
1955
  continue;
@@ -2116,9 +2108,6 @@ function restoreFromTerseSummaries(comparisonConfig, terseSummaries) {
2116
2108
  return allTestSummaries;
2117
2109
  }
2118
2110
 
2119
- // src/comparison/report/comparison-grouping.ts
2120
- import { assertNever as assertNever8 } from "assert-never";
2121
-
2122
2111
  // src/comparison/report/buckets.ts
2123
2112
  function getBucketIndex(diffPct, thresholds) {
2124
2113
  if (diffPct === 0) {
@@ -2132,7 +2121,33 @@ function getBucketIndex(diffPct, thresholds) {
2132
2121
  return thresholds.length + 1;
2133
2122
  }
2134
2123
 
2124
+ // src/comparison/report/comparison-group-scores.ts
2125
+ function getScoresForTestSummaries(testSummaries, thresholds) {
2126
+ const diffCountByBucket = Array(thresholds.length + 2).fill(0);
2127
+ const totalMaxDiffByBucket = Array(thresholds.length + 2).fill(0);
2128
+ let totalDiffCount = 0;
2129
+ for (const testSummary of testSummaries) {
2130
+ const bucketIndex = getBucketIndex(testSummary.md, thresholds);
2131
+ diffCountByBucket[bucketIndex]++;
2132
+ totalMaxDiffByBucket[bucketIndex] += testSummary.md;
2133
+ totalDiffCount++;
2134
+ }
2135
+ let diffPercentByBucket;
2136
+ if (totalDiffCount > 0) {
2137
+ diffPercentByBucket = diffCountByBucket.map((count) => count / totalDiffCount * 100);
2138
+ } else {
2139
+ diffPercentByBucket = [];
2140
+ }
2141
+ return {
2142
+ totalDiffCount,
2143
+ totalMaxDiffByBucket,
2144
+ diffCountByBucket,
2145
+ diffPercentByBucket
2146
+ };
2147
+ }
2148
+
2135
2149
  // src/comparison/report/comparison-grouping.ts
2150
+ import { assertNever as assertNever8 } from "assert-never";
2136
2151
  function categorizeComparisonTestSummaries(comparisonConfig, terseSummaries) {
2137
2152
  const allTestSummaries = restoreFromTerseSummaries(comparisonConfig, terseSummaries);
2138
2153
  const groupsByScenario = groupComparisonTestSummaries(allTestSummaries, "by-scenario");
@@ -2140,6 +2155,7 @@ function categorizeComparisonTestSummaries(comparisonConfig, terseSummaries) {
2140
2155
  const groupsByDataset = groupComparisonTestSummaries(allTestSummaries, "by-dataset");
2141
2156
  const byDataset = categorizeComparisonGroups(comparisonConfig, [...groupsByDataset.values()]);
2142
2157
  return {
2158
+ allTestSummaries,
2143
2159
  byScenario,
2144
2160
  byDataset
2145
2161
  };
@@ -2181,7 +2197,7 @@ function categorizeComparisonGroups(comparisonConfig, allGroups) {
2181
2197
  function addSummaryForGroup(group, root, validInL, validInR) {
2182
2198
  let scores;
2183
2199
  if (validInL && validInR) {
2184
- scores = getScoresForGroup(group, comparisonConfig.thresholds);
2200
+ scores = getScoresForTestSummaries(group.testSummaries, comparisonConfig.thresholds);
2185
2201
  }
2186
2202
  const groupSummary = {
2187
2203
  root,
@@ -2239,29 +2255,6 @@ function categorizeComparisonGroups(comparisonConfig, allGroups) {
2239
2255
  withoutDiffs
2240
2256
  };
2241
2257
  }
2242
- function getScoresForGroup(group, thresholds) {
2243
- const diffCountByBucket = Array(thresholds.length + 2).fill(0);
2244
- const totalMaxDiffByBucket = Array(thresholds.length + 2).fill(0);
2245
- let totalDiffCount = 0;
2246
- for (const testSummary of group.testSummaries) {
2247
- const bucketIndex = getBucketIndex(testSummary.md, thresholds);
2248
- diffCountByBucket[bucketIndex]++;
2249
- totalMaxDiffByBucket[bucketIndex] += testSummary.md;
2250
- totalDiffCount++;
2251
- }
2252
- let diffPercentByBucket;
2253
- if (totalDiffCount > 0) {
2254
- diffPercentByBucket = diffCountByBucket.map((count) => count / totalDiffCount * 100);
2255
- } else {
2256
- diffPercentByBucket = [];
2257
- }
2258
- return {
2259
- totalDiffCount,
2260
- totalMaxDiffByBucket,
2261
- diffCountByBucket,
2262
- diffPercentByBucket
2263
- };
2264
- }
2265
2258
  function sortDatasetGroupSummaries(summaries) {
2266
2259
  return summaries.sort((a, b) => {
2267
2260
  var _a, _b;
@@ -2321,57 +2314,6 @@ function compareScores(a, b) {
2321
2314
  return 0;
2322
2315
  }
2323
2316
 
2324
- // src/bundle/model-inputs.ts
2325
- var ModelInputs = class {
2326
- constructor(modelSpec) {
2327
- /** All inputs keyed by lookup name (lowercase variable name or alias). */
2328
- this.inputsByLookupName = /* @__PURE__ */ new Map();
2329
- /** All input ID aliases. */
2330
- this.inputIdAliases = /* @__PURE__ */ new Set();
2331
- for (const inputVar of modelSpec.inputVars.values()) {
2332
- const varNameKey = inputVar.varName.toLowerCase();
2333
- this.inputsByLookupName.set(varNameKey, inputVar);
2334
- if (inputVar.inputId) {
2335
- const idAlias = `id ${inputVar.inputId}`;
2336
- this.inputIdAliases.add(idAlias);
2337
- const idKey = idAlias.toLowerCase();
2338
- this.inputsByLookupName.set(idKey, inputVar);
2339
- }
2340
- }
2341
- if (modelSpec.inputAliases) {
2342
- for (const [alias, varId] of modelSpec.inputAliases.entries()) {
2343
- const aliasKey = alias.toLowerCase();
2344
- if (this.inputsByLookupName.has(aliasKey)) {
2345
- console.warn(
2346
- `WARNING: Input variable already defined with a name that collides with alias '${alias}', skipping`
2347
- );
2348
- continue;
2349
- }
2350
- const inputVar = modelSpec.inputVars.get(varId);
2351
- if (inputVar === void 0) {
2352
- console.warn(`WARNING: No input variable found for '${varId}' associated with alias '${alias}', skipping`);
2353
- continue;
2354
- }
2355
- this.inputsByLookupName.set(aliasKey, inputVar);
2356
- }
2357
- }
2358
- }
2359
- /**
2360
- * Return the set of `id XYZ` aliases for all input variables.
2361
- */
2362
- getAllInputIdAliases() {
2363
- return [...this.inputIdAliases];
2364
- }
2365
- /**
2366
- * Return the `InputVar` that matches the requested name (either by variable name or alias).
2367
- *
2368
- * @param name The variable name or alias to match.
2369
- */
2370
- getInputVarForName(name) {
2371
- return this.inputsByLookupName.get(name.toLowerCase());
2372
- }
2373
- };
2374
-
2375
2317
  // src/comparison/config/parse/comparison-parser.ts
2376
2318
  import Ajv2 from "ajv";
2377
2319
  import assertNever9 from "assert-never";
@@ -2395,10 +2337,27 @@ var comparison_schema_default = {
2395
2337
  oneOf: [
2396
2338
  { $ref: "#/$defs/scenario_array_item" },
2397
2339
  { $ref: "#/$defs/scenario_group_array_item" },
2340
+ { $ref: "#/$defs/graph_group_array_item" },
2398
2341
  { $ref: "#/$defs/view_group_array_item" }
2399
2342
  ]
2400
2343
  },
2401
2344
  //
2345
+ // DATASETS
2346
+ //
2347
+ dataset: {
2348
+ type: "object",
2349
+ additionalProperties: false,
2350
+ properties: {
2351
+ name: {
2352
+ type: "string"
2353
+ },
2354
+ source: {
2355
+ type: "string"
2356
+ }
2357
+ },
2358
+ required: ["name"]
2359
+ },
2360
+ //
2402
2361
  // SCENARIOS
2403
2362
  //
2404
2363
  scenario_array_item: {
@@ -2638,9 +2597,62 @@ var comparison_schema_default = {
2638
2597
  required: ["scenario_group_ref"]
2639
2598
  },
2640
2599
  //
2600
+ // GRAPHS
2601
+ //
2602
+ graphs_preset: {
2603
+ type: "string",
2604
+ enum: ["all"]
2605
+ },
2606
+ graphs_array: {
2607
+ type: "array",
2608
+ items: {
2609
+ type: "string"
2610
+ },
2611
+ minItems: 1
2612
+ },
2613
+ graph_group_ref: {
2614
+ type: "object",
2615
+ additionalProperties: false,
2616
+ properties: {
2617
+ graph_group_ref: {
2618
+ type: "string"
2619
+ }
2620
+ },
2621
+ required: ["graph_group_ref"]
2622
+ },
2623
+ //
2624
+ // GRAPH GROUPS
2625
+ //
2626
+ graph_group_array_item: {
2627
+ type: "object",
2628
+ additionalProperties: false,
2629
+ properties: {
2630
+ graph_group: {
2631
+ $ref: "#/$defs/graph_group"
2632
+ }
2633
+ },
2634
+ required: ["graph_group"]
2635
+ },
2636
+ graph_group: {
2637
+ type: "object",
2638
+ additionalProperties: false,
2639
+ properties: {
2640
+ id: {
2641
+ type: "string"
2642
+ },
2643
+ graphs: {
2644
+ $ref: "#/$defs/graphs_array"
2645
+ }
2646
+ },
2647
+ required: ["id", "graphs"]
2648
+ },
2649
+ //
2641
2650
  // VIEWS
2642
2651
  //
2643
2652
  view: {
2653
+ oneOf: [{ $ref: "#/$defs/view_with_scenario" }, { $ref: "#/$defs/view_with_rows" }]
2654
+ },
2655
+ view_with_scenario: {
2644
2656
  type: "object",
2645
2657
  additionalProperties: false,
2646
2658
  properties: {
@@ -2655,24 +2667,103 @@ var comparison_schema_default = {
2655
2667
  },
2656
2668
  graphs: {
2657
2669
  $ref: "#/$defs/view_graphs"
2670
+ },
2671
+ graph_order: {
2672
+ $ref: "#/$defs/view_graph_order"
2658
2673
  }
2659
2674
  },
2660
- required: ["scenario_ref", "graphs"]
2675
+ required: ["scenario_ref"]
2661
2676
  },
2662
- view_graphs: {
2663
- oneOf: [{ $ref: "#/$defs/view_graphs_preset" }, { $ref: "#/$defs/view_graphs_array" }]
2677
+ view_with_rows: {
2678
+ type: "object",
2679
+ additionalProperties: false,
2680
+ properties: {
2681
+ title: {
2682
+ type: "string"
2683
+ },
2684
+ subtitle: {
2685
+ type: "string"
2686
+ },
2687
+ rows: {
2688
+ $ref: "#/$defs/view_rows_array"
2689
+ }
2690
+ },
2691
+ required: ["title", "rows"]
2664
2692
  },
2665
- view_graphs_preset: {
2666
- type: "string",
2667
- enum: ["all"]
2693
+ view_rows_array: {
2694
+ type: "array",
2695
+ items: {
2696
+ $ref: "#/$defs/view_rows_array_item"
2697
+ },
2698
+ minItems: 1
2699
+ },
2700
+ view_rows_array_item: {
2701
+ type: "object",
2702
+ additionalProperties: false,
2703
+ properties: {
2704
+ row: {
2705
+ $ref: "#/$defs/view_row"
2706
+ }
2707
+ }
2708
+ },
2709
+ view_row: {
2710
+ type: "object",
2711
+ additionalProperties: false,
2712
+ properties: {
2713
+ title: {
2714
+ type: "string"
2715
+ },
2716
+ subtitle: {
2717
+ type: "string"
2718
+ },
2719
+ boxes: {
2720
+ $ref: "#/$defs/view_boxes_array"
2721
+ }
2722
+ },
2723
+ required: ["title", "boxes"]
2668
2724
  },
2669
- view_graphs_array: {
2725
+ view_boxes_array: {
2670
2726
  type: "array",
2671
2727
  items: {
2672
- type: "string"
2728
+ $ref: "#/$defs/view_boxes_array_item"
2673
2729
  },
2674
2730
  minItems: 1
2675
2731
  },
2732
+ view_boxes_array_item: {
2733
+ type: "object",
2734
+ additionalProperties: false,
2735
+ properties: {
2736
+ box: {
2737
+ $ref: "#/$defs/view_box"
2738
+ }
2739
+ }
2740
+ },
2741
+ view_box: {
2742
+ type: "object",
2743
+ additionalProperties: false,
2744
+ properties: {
2745
+ title: {
2746
+ type: "string"
2747
+ },
2748
+ subtitle: {
2749
+ type: "string"
2750
+ },
2751
+ dataset: {
2752
+ $ref: "#/$defs/dataset"
2753
+ },
2754
+ scenario_ref: {
2755
+ $ref: "#/$defs/scenario_ref_id"
2756
+ }
2757
+ },
2758
+ required: ["title", "dataset", "scenario_ref"]
2759
+ },
2760
+ view_graphs: {
2761
+ oneOf: [{ $ref: "#/$defs/graphs_preset" }, { $ref: "#/$defs/graphs_array" }, { $ref: "#/$defs/graph_group_ref" }]
2762
+ },
2763
+ view_graph_order: {
2764
+ type: "string",
2765
+ enum: ["default", "grouped-by-diffs"]
2766
+ },
2676
2767
  //
2677
2768
  // VIEW GROUPS
2678
2769
  //
@@ -2748,6 +2839,7 @@ var comparison_schema_default = {
2748
2839
  function parseComparisonSpecs(specSource) {
2749
2840
  const scenarios = [];
2750
2841
  const scenarioGroups = [];
2842
+ const graphGroups = [];
2751
2843
  const viewGroups = [];
2752
2844
  const ajv = new Ajv2();
2753
2845
  const validate = ajv.compile(comparison_schema_default);
@@ -2768,6 +2860,8 @@ function parseComparisonSpecs(specSource) {
2768
2860
  scenarios.push(scenarioSpecFromParsed(specItem.scenario));
2769
2861
  } else if ("scenario_group" in specItem) {
2770
2862
  scenarioGroups.push(scenarioGroupSpecFromParsed(specItem.scenario_group));
2863
+ } else if ("graph_group" in specItem) {
2864
+ graphGroups.push(graphGroupSpecFromParsed(specItem.graph_group));
2771
2865
  } else if ("view_group" in specItem) {
2772
2866
  viewGroups.push(viewGroupSpecFromParsed(specItem.view_group));
2773
2867
  }
@@ -2785,9 +2879,17 @@ ${error.message}`;
2785
2879
  return ok2({
2786
2880
  scenarios,
2787
2881
  scenarioGroups,
2882
+ graphGroups,
2788
2883
  viewGroups
2789
2884
  });
2790
2885
  }
2886
+ function datasetSpecFromParsed(parsedDataset) {
2887
+ return {
2888
+ kind: "dataset",
2889
+ name: parsedDataset.name,
2890
+ source: parsedDataset.source
2891
+ };
2892
+ }
2791
2893
  function scenarioSpecFromParsed(parsedScenario) {
2792
2894
  if (parsedScenario.preset === "matrix") {
2793
2895
  return {
@@ -2880,13 +2982,50 @@ function scenarioGroupRefSpecFromParsed(parsedGroupRef) {
2880
2982
  groupId: parsedGroupRef.scenario_group_ref
2881
2983
  };
2882
2984
  }
2985
+ function graphGroupSpecFromParsed(parsedGraphGroup) {
2986
+ return {
2987
+ kind: "graph-group",
2988
+ id: parsedGraphGroup.id,
2989
+ graphIds: parsedGraphGroup.graphs
2990
+ };
2991
+ }
2883
2992
  function viewSpecFromParsed(parsedView) {
2993
+ let scenarioId;
2994
+ let rows;
2995
+ if (parsedView.scenario_ref !== void 0) {
2996
+ scenarioId = parsedView.scenario_ref;
2997
+ } else if (parsedView.rows !== void 0) {
2998
+ rows = parsedView.rows.map((item) => viewRowSpecFromParsed(item.row));
2999
+ }
3000
+ let graphs;
3001
+ if (parsedView.graphs !== void 0) {
3002
+ graphs = viewGraphsSpecFromParsed(parsedView.graphs);
3003
+ }
2884
3004
  return {
2885
3005
  kind: "view",
2886
3006
  title: parsedView.title,
2887
3007
  subtitle: parsedView.subtitle,
2888
- scenarioId: parsedView.scenario_ref,
2889
- graphs: viewGraphsSpecFromParsed(parsedView.graphs)
3008
+ scenarioId,
3009
+ rows,
3010
+ graphs,
3011
+ graphOrder: parsedView.graph_order
3012
+ };
3013
+ }
3014
+ function viewRowSpecFromParsed(parsedRow) {
3015
+ return {
3016
+ kind: "view-row",
3017
+ title: parsedRow.title,
3018
+ subtitle: parsedRow.subtitle,
3019
+ boxes: parsedRow.boxes.map((item) => viewBoxSpecFromParsed(item.box))
3020
+ };
3021
+ }
3022
+ function viewBoxSpecFromParsed(parsedBox) {
3023
+ return {
3024
+ kind: "view-box",
3025
+ title: parsedBox.title,
3026
+ subtitle: parsedBox.subtitle,
3027
+ dataset: datasetSpecFromParsed(parsedBox.dataset),
3028
+ scenarioId: parsedBox.scenario_ref
2890
3029
  };
2891
3030
  }
2892
3031
  function viewGraphsSpecFromParsed(parsedGraphs) {
@@ -2895,11 +3034,18 @@ function viewGraphsSpecFromParsed(parsedGraphs) {
2895
3034
  kind: "graphs-preset",
2896
3035
  preset: "all"
2897
3036
  };
2898
- } else {
3037
+ } else if (Array.isArray(parsedGraphs)) {
2899
3038
  return {
2900
3039
  kind: "graphs-array",
2901
3040
  graphIds: parsedGraphs
2902
3041
  };
3042
+ } else if ("graph_group_ref" in parsedGraphs) {
3043
+ return {
3044
+ kind: "graph-group-ref",
3045
+ groupId: parsedGraphs.graph_group_ref
3046
+ };
3047
+ } else {
3048
+ throw new Error("Invalid graphs spec in comparison view");
2903
3049
  }
2904
3050
  }
2905
3051
  function viewGroupSpecFromParsed(parsedViewGroup) {
@@ -2935,6 +3081,57 @@ function viewGroupSpecFromParsed(parsedViewGroup) {
2935
3081
  // src/comparison/config/resolve/comparison-resolver.ts
2936
3082
  import { assertNever as assertNever11 } from "assert-never";
2937
3083
 
3084
+ // src/bundle/model-inputs.ts
3085
+ var ModelInputs = class {
3086
+ constructor(modelSpec) {
3087
+ /** All inputs keyed by lookup name (lowercase variable name or alias). */
3088
+ this.inputsByLookupName = /* @__PURE__ */ new Map();
3089
+ /** All input ID aliases. */
3090
+ this.inputIdAliases = /* @__PURE__ */ new Set();
3091
+ for (const inputVar of modelSpec.inputVars.values()) {
3092
+ const varNameKey = inputVar.varName.toLowerCase();
3093
+ this.inputsByLookupName.set(varNameKey, inputVar);
3094
+ if (inputVar.inputId) {
3095
+ const idAlias = `id ${inputVar.inputId}`;
3096
+ this.inputIdAliases.add(idAlias);
3097
+ const idKey = idAlias.toLowerCase();
3098
+ this.inputsByLookupName.set(idKey, inputVar);
3099
+ }
3100
+ }
3101
+ if (modelSpec.inputAliases) {
3102
+ for (const [alias, varId] of modelSpec.inputAliases.entries()) {
3103
+ const aliasKey = alias.toLowerCase();
3104
+ if (this.inputsByLookupName.has(aliasKey)) {
3105
+ console.warn(
3106
+ `WARNING: Input variable already defined with a name that collides with alias '${alias}', skipping`
3107
+ );
3108
+ continue;
3109
+ }
3110
+ const inputVar = modelSpec.inputVars.get(varId);
3111
+ if (inputVar === void 0) {
3112
+ console.warn(`WARNING: No input variable found for '${varId}' associated with alias '${alias}', skipping`);
3113
+ continue;
3114
+ }
3115
+ this.inputsByLookupName.set(aliasKey, inputVar);
3116
+ }
3117
+ }
3118
+ }
3119
+ /**
3120
+ * Return the set of `id XYZ` aliases for all input variables.
3121
+ */
3122
+ getAllInputIdAliases() {
3123
+ return [...this.inputIdAliases];
3124
+ }
3125
+ /**
3126
+ * Return the `InputVar` that matches the requested name (either by variable name or alias).
3127
+ *
3128
+ * @param name The variable name or alias to match.
3129
+ */
3130
+ getInputVarForName(name) {
3131
+ return this.inputsByLookupName.get(name.toLowerCase());
3132
+ }
3133
+ };
3134
+
2938
3135
  // src/comparison/config/resolve/comparison-scenario-specs.ts
2939
3136
  import { assertNever as assertNever10 } from "assert-never";
2940
3137
  function scenarioSpecsFromSettings(settings) {
@@ -2973,17 +3170,20 @@ function inputSettingFromResolvedInputState(state) {
2973
3170
  }
2974
3171
 
2975
3172
  // src/comparison/config/resolve/comparison-resolver.ts
2976
- function resolveComparisonSpecs(modelInputsL, modelInputsR, specs) {
3173
+ function resolveComparisonSpecs(modelSpecL, modelSpecR, specs) {
2977
3174
  let key = 1;
2978
3175
  const genKey = () => {
2979
3176
  return `${key++}`;
2980
3177
  };
3178
+ const modelInputsL = new ModelInputs(modelSpecL);
3179
+ const modelInputsR = new ModelInputs(modelSpecR);
3180
+ const modelOutputs = new ModelOutputs(modelSpecL, modelSpecR);
2981
3181
  const resolvedScenarios = new ResolvedScenarios();
2982
- for (const scenarioSpec of specs.scenarios) {
3182
+ for (const scenarioSpec of specs.scenarios || []) {
2983
3183
  resolvedScenarios.add(resolveScenariosFromSpec(modelInputsL, modelInputsR, scenarioSpec, genKey));
2984
3184
  }
2985
3185
  const partiallyResolvedScenarioGroups = [];
2986
- for (const scenarioGroupSpec of specs.scenarioGroups) {
3186
+ for (const scenarioGroupSpec of specs.scenarioGroups || []) {
2987
3187
  const scenariosForGroup = [];
2988
3188
  for (const scenarioItem of scenarioGroupSpec.scenarios) {
2989
3189
  if (scenarioItem.kind === "scenario-ref") {
@@ -3031,9 +3231,21 @@ function resolveComparisonSpecs(modelInputsL, modelInputsR, specs) {
3031
3231
  scenarios: scenariosForGroup
3032
3232
  });
3033
3233
  }
3234
+ const resolvedGraphGroups = new ResolvedGraphGroups();
3235
+ for (const graphGroupSpec of specs.graphGroups || []) {
3236
+ resolvedGraphGroups.add(graphGroupSpec);
3237
+ }
3034
3238
  const resolvedViewGroups = [];
3035
- for (const viewGroupSpec of specs.viewGroups) {
3036
- const resolvedViewGroup = resolveViewGroupFromSpec(resolvedScenarios, resolvedScenarioGroups, viewGroupSpec);
3239
+ for (const viewGroupSpec of specs.viewGroups || []) {
3240
+ const resolvedViewGroup = resolveViewGroupFromSpec(
3241
+ modelSpecL,
3242
+ modelSpecR,
3243
+ modelOutputs,
3244
+ resolvedScenarios,
3245
+ resolvedScenarioGroups,
3246
+ resolvedGraphGroups,
3247
+ viewGroupSpec
3248
+ );
3037
3249
  resolvedViewGroups.push(resolvedViewGroup);
3038
3250
  }
3039
3251
  return {
@@ -3042,6 +3254,35 @@ function resolveComparisonSpecs(modelInputsL, modelInputsR, specs) {
3042
3254
  viewGroups: resolvedViewGroups
3043
3255
  };
3044
3256
  }
3257
+ var ModelOutputs = class {
3258
+ constructor(modelSpecL, modelSpecR) {
3259
+ this.modelSpecL = modelSpecL;
3260
+ this.modelSpecR = modelSpecR;
3261
+ }
3262
+ getDatasetForName(name, source) {
3263
+ function findOutputVar(modelSpec) {
3264
+ for (const outputVar of modelSpec.outputVars.values()) {
3265
+ if (outputVar.varName === name && outputVar.sourceName === source) {
3266
+ return outputVar;
3267
+ }
3268
+ }
3269
+ return void 0;
3270
+ }
3271
+ const outputVarR = findOutputVar(this.modelSpecR);
3272
+ if (outputVarR) {
3273
+ const datasetKey = outputVarR.datasetKey;
3274
+ const outputVarL = this.modelSpecL.outputVars.get(datasetKey);
3275
+ return {
3276
+ kind: "dataset",
3277
+ key: datasetKey,
3278
+ outputVarL,
3279
+ outputVarR
3280
+ };
3281
+ } else {
3282
+ return void 0;
3283
+ }
3284
+ }
3285
+ };
3045
3286
  var ResolvedScenarios = class {
3046
3287
  constructor() {
3047
3288
  /** The array of all resolved scenarios. */
@@ -3335,33 +3576,73 @@ var ResolvedScenarioGroups = class {
3335
3576
  return this.resolvedGroupsById.get(id);
3336
3577
  }
3337
3578
  };
3338
- function resolveGraphsFromSpec(graphsSpec) {
3579
+ var ResolvedGraphGroups = class {
3580
+ constructor() {
3581
+ /** The set of resolved graph groups, keyed by ID. */
3582
+ this.resolvedGroupsById = /* @__PURE__ */ new Map();
3583
+ }
3584
+ add(group) {
3585
+ if (this.resolvedGroupsById.has(group.id)) {
3586
+ throw new Error(`Multiple graph groups defined with the same id (${group.id})`);
3587
+ }
3588
+ this.resolvedGroupsById.set(group.id, group);
3589
+ }
3590
+ getGroupForId(id) {
3591
+ return this.resolvedGroupsById.get(id);
3592
+ }
3593
+ };
3594
+ function resolveGraphsFromSpec(modelSpecL, modelSpecR, resolvedGraphGroups, graphsSpec) {
3339
3595
  switch (graphsSpec.kind) {
3340
- case "graphs-preset":
3341
- return "all";
3596
+ case "graphs-preset": {
3597
+ switch (graphsSpec.preset) {
3598
+ case "all": {
3599
+ const graphIds = /* @__PURE__ */ new Set();
3600
+ const addGraphIds = (modelSpec) => {
3601
+ if (modelSpec.graphSpecs) {
3602
+ for (const graphSpec of modelSpec.graphSpecs) {
3603
+ graphIds.add(graphSpec.id);
3604
+ }
3605
+ }
3606
+ };
3607
+ addGraphIds(modelSpecL);
3608
+ addGraphIds(modelSpecR);
3609
+ return [...graphIds];
3610
+ }
3611
+ default:
3612
+ assertNever11(graphsSpec.preset);
3613
+ }
3614
+ }
3615
+ // eslint-disable-next-line no-fallthrough
3342
3616
  case "graphs-array":
3343
3617
  return graphsSpec.graphIds;
3618
+ case "graph-group-ref": {
3619
+ const groupSpec = resolvedGraphGroups.getGroupForId(graphsSpec.groupId);
3620
+ if (groupSpec === void 0) {
3621
+ throw new Error(`No graph group found for id ${graphsSpec.groupId}`);
3622
+ }
3623
+ return groupSpec.graphIds;
3624
+ }
3344
3625
  default:
3345
3626
  assertNever11(graphsSpec);
3346
3627
  }
3347
3628
  }
3348
- function resolveViewForScenarioId(resolvedScenarios, viewTitle, viewSubtitle, scenarioId, graphs) {
3629
+ function resolveViewForScenarioId(resolvedScenarios, viewTitle, viewSubtitle, scenarioId, graphIds, graphOrder) {
3349
3630
  const resolvedScenario = resolvedScenarios.getScenarioForId(scenarioId);
3350
3631
  if (resolvedScenario) {
3351
- return resolveViewForScenario(viewTitle, viewSubtitle, resolvedScenario, graphs);
3632
+ return resolveViewForScenario(viewTitle, viewSubtitle, resolvedScenario, graphIds, graphOrder);
3352
3633
  } else {
3353
3634
  return unresolvedViewForScenarioId(viewTitle, viewSubtitle, scenarioId);
3354
3635
  }
3355
3636
  }
3356
- function resolveViewForScenarioRefSpec(resolvedScenarios, refSpec, graphs) {
3637
+ function resolveViewForScenarioRefSpec(resolvedScenarios, refSpec, graphIds, graphOrder) {
3357
3638
  const resolvedScenario = resolvedScenarios.getScenarioForId(refSpec.scenarioId);
3358
3639
  if (resolvedScenario) {
3359
- return resolveViewForScenario(refSpec.title, refSpec.subtitle, resolvedScenario, graphs);
3640
+ return resolveViewForScenario(refSpec.title, refSpec.subtitle, resolvedScenario, graphIds, graphOrder);
3360
3641
  } else {
3361
3642
  return unresolvedViewForScenarioId(void 0, void 0, refSpec.scenarioId);
3362
3643
  }
3363
3644
  }
3364
- function resolveViewForScenario(viewTitle, viewSubtitle, resolvedScenario, graphs) {
3645
+ function resolveViewForScenario(viewTitle, viewSubtitle, resolvedScenario, graphIds, graphOrder) {
3365
3646
  if (viewTitle === void 0) {
3366
3647
  viewTitle = resolvedScenario.title;
3367
3648
  if (viewTitle === void 0) {
@@ -3376,15 +3657,83 @@ function resolveViewForScenario(viewTitle, viewSubtitle, resolvedScenario, graph
3376
3657
  title: viewTitle,
3377
3658
  subtitle: viewSubtitle,
3378
3659
  scenario: resolvedScenario,
3379
- graphs
3660
+ graphIds,
3661
+ graphOrder
3380
3662
  };
3381
3663
  }
3382
- function unresolvedViewForScenarioId(viewTitle, viewSubtitle, scenarioId) {
3664
+ function resolveViewBoxForSpec(modelOutputs, resolvedScenarios, boxSpec) {
3665
+ const resolvedDataset = modelOutputs.getDatasetForName(boxSpec.dataset.name, boxSpec.dataset.source);
3666
+ if (resolvedDataset === void 0) {
3667
+ return {
3668
+ kind: "unresolved-view-box",
3669
+ datasetName: boxSpec.dataset.name,
3670
+ datasetSource: boxSpec.dataset.source
3671
+ };
3672
+ }
3673
+ const resolvedScenario = resolvedScenarios.getScenarioForId(boxSpec.scenarioId);
3674
+ if (resolvedScenario === void 0) {
3675
+ return {
3676
+ kind: "unresolved-view-box",
3677
+ scenarioId: boxSpec.scenarioId
3678
+ };
3679
+ }
3680
+ return {
3681
+ kind: "view-box",
3682
+ title: boxSpec.title,
3683
+ subtitle: boxSpec.subtitle,
3684
+ dataset: resolvedDataset,
3685
+ scenario: resolvedScenario
3686
+ };
3687
+ }
3688
+ function resolveViewRowForSpec(modelOutputs, resolvedScenarios, rowSpec) {
3689
+ const resolvedBoxes = [];
3690
+ for (const boxSpec of rowSpec.boxes) {
3691
+ const box = resolveViewBoxForSpec(modelOutputs, resolvedScenarios, boxSpec);
3692
+ if (box.kind === "view-box") {
3693
+ resolvedBoxes.push(box);
3694
+ } else {
3695
+ return box;
3696
+ }
3697
+ }
3698
+ return {
3699
+ kind: "view-row",
3700
+ title: rowSpec.title,
3701
+ subtitle: rowSpec.subtitle,
3702
+ boxes: resolvedBoxes
3703
+ };
3704
+ }
3705
+ function resolveViewWithRowSpecs(modelOutputs, resolvedScenarios, viewTitle, viewSubtitle, rowSpecs) {
3706
+ const resolvedRows = [];
3707
+ for (const rowSpec of rowSpecs) {
3708
+ const row = resolveViewRowForSpec(modelOutputs, resolvedScenarios, rowSpec);
3709
+ if (row.kind === "view-row") {
3710
+ resolvedRows.push(row);
3711
+ } else {
3712
+ return unresolvedViewForScenarioId(viewTitle, viewSubtitle, row.scenarioId, row.datasetName, row.datasetSource);
3713
+ }
3714
+ }
3715
+ if (viewTitle === void 0) {
3716
+ viewTitle = "Untitled view";
3717
+ }
3718
+ return {
3719
+ kind: "view",
3720
+ title: viewTitle,
3721
+ subtitle: viewSubtitle,
3722
+ rows: resolvedRows,
3723
+ // TODO: The schema doesn't currently allow for graphs in a view that is defined
3724
+ // with rows. Probably we should have different view types to make this more clear.
3725
+ graphIds: [],
3726
+ graphOrder: "default"
3727
+ };
3728
+ }
3729
+ function unresolvedViewForScenarioId(viewTitle, viewSubtitle, scenarioId, datasetName, datasetSource) {
3383
3730
  return {
3384
3731
  kind: "unresolved-view",
3385
3732
  title: viewTitle,
3386
3733
  subtitle: viewSubtitle,
3387
- scenarioId
3734
+ scenarioId,
3735
+ datasetName,
3736
+ datasetSource
3388
3737
  };
3389
3738
  }
3390
3739
  function unresolvedViewForScenarioGroupId(viewTitle, viewSubtitle, scenarioGroupId) {
@@ -3395,29 +3744,46 @@ function unresolvedViewForScenarioGroupId(viewTitle, viewSubtitle, scenarioGroup
3395
3744
  scenarioGroupId
3396
3745
  };
3397
3746
  }
3398
- function resolveViewGroupFromSpec(resolvedScenarios, resolvedScenarioGroups, viewGroupSpec) {
3747
+ function resolveViewGroupFromSpec(modelSpecL, modelSpecR, modelOutputs, resolvedScenarios, resolvedScenarioGroups, resolvedGraphGroups, viewGroupSpec) {
3399
3748
  let views;
3400
3749
  switch (viewGroupSpec.kind) {
3401
3750
  case "view-group-with-views": {
3402
3751
  views = viewGroupSpec.views.map((viewSpec) => {
3403
- const graphs = resolveGraphsFromSpec(viewSpec.graphs);
3404
- return resolveViewForScenarioId(
3405
- resolvedScenarios,
3406
- viewSpec.title,
3407
- viewSpec.subtitle,
3408
- viewSpec.scenarioId,
3409
- graphs
3410
- );
3752
+ if (viewSpec.scenarioId) {
3753
+ let graphIds;
3754
+ if (viewSpec.graphs) {
3755
+ graphIds = resolveGraphsFromSpec(modelSpecL, modelSpecR, resolvedGraphGroups, viewSpec.graphs);
3756
+ } else {
3757
+ graphIds = [];
3758
+ }
3759
+ return resolveViewForScenarioId(
3760
+ resolvedScenarios,
3761
+ viewSpec.title,
3762
+ viewSpec.subtitle,
3763
+ viewSpec.scenarioId,
3764
+ graphIds,
3765
+ viewSpec.graphOrder || "default"
3766
+ );
3767
+ } else {
3768
+ return resolveViewWithRowSpecs(
3769
+ modelOutputs,
3770
+ resolvedScenarios,
3771
+ viewSpec.title,
3772
+ viewSpec.subtitle,
3773
+ viewSpec.rows
3774
+ );
3775
+ }
3411
3776
  });
3412
3777
  break;
3413
3778
  }
3414
3779
  case "view-group-with-scenarios": {
3415
- const graphs = resolveGraphsFromSpec(viewGroupSpec.graphs);
3780
+ const graphIds = resolveGraphsFromSpec(modelSpecL, modelSpecR, resolvedGraphGroups, viewGroupSpec.graphs);
3781
+ const graphOrder = viewGroupSpec.graphOrder || "default";
3416
3782
  views = [];
3417
3783
  for (const refSpec of viewGroupSpec.scenarios) {
3418
3784
  switch (refSpec.kind) {
3419
3785
  case "scenario-ref":
3420
- views.push(resolveViewForScenarioRefSpec(resolvedScenarios, refSpec, graphs));
3786
+ views.push(resolveViewForScenarioRefSpec(resolvedScenarios, refSpec, graphIds, graphOrder));
3421
3787
  break;
3422
3788
  case "scenario-group-ref": {
3423
3789
  const resolvedGroup = resolvedScenarioGroups.getGroupForId(refSpec.groupId);
@@ -3428,7 +3794,7 @@ function resolveViewGroupFromSpec(resolvedScenarios, resolvedScenarioGroups, vie
3428
3794
  views.push(unresolvedViewForScenarioId(void 0, void 0, scenario.scenarioId));
3429
3795
  break;
3430
3796
  case "scenario":
3431
- views.push(resolveViewForScenario(void 0, void 0, scenario, graphs));
3797
+ views.push(resolveViewForScenario(void 0, void 0, scenario, graphIds, graphOrder));
3432
3798
  break;
3433
3799
  default:
3434
3800
  assertNever11(scenario);
@@ -3456,10 +3822,11 @@ function resolveViewGroupFromSpec(resolvedScenarios, resolvedScenarioGroups, vie
3456
3822
  }
3457
3823
 
3458
3824
  // src/comparison/config/comparison-config.ts
3459
- function resolveComparisonSpecsFromSources(modelInputsL, modelInputsR, specSources) {
3825
+ function resolveComparisonSpecsFromSources(modelSpecL, modelSpecR, specSources) {
3460
3826
  const combinedSpecs = {
3461
3827
  scenarios: [],
3462
3828
  scenarioGroups: [],
3829
+ graphGroups: [],
3463
3830
  viewGroups: []
3464
3831
  };
3465
3832
  for (const specSource of specSources) {
@@ -3476,11 +3843,12 @@ function resolveComparisonSpecsFromSources(modelInputsL, modelInputsR, specSourc
3476
3843
  } else {
3477
3844
  specs = specSource;
3478
3845
  }
3479
- combinedSpecs.scenarios.push(...specs.scenarios);
3480
- combinedSpecs.scenarioGroups.push(...specs.scenarioGroups);
3481
- combinedSpecs.viewGroups.push(...specs.viewGroups);
3846
+ combinedSpecs.scenarios.push(...specs.scenarios || []);
3847
+ combinedSpecs.scenarioGroups.push(...specs.scenarioGroups || []);
3848
+ combinedSpecs.graphGroups.push(...specs.graphGroups || []);
3849
+ combinedSpecs.viewGroups.push(...specs.viewGroups || []);
3482
3850
  }
3483
- return resolveComparisonSpecs(modelInputsL, modelInputsR, combinedSpecs);
3851
+ return resolveComparisonSpecs(modelSpecL, modelSpecR, combinedSpecs);
3484
3852
  }
3485
3853
 
3486
3854
  // src/comparison/config/comparison-datasets.ts
@@ -3494,6 +3862,8 @@ var ComparisonDatasetsImpl = class {
3494
3862
  * @param datasetOptions The custom configuration for the datasets to be compared.
3495
3863
  */
3496
3864
  constructor(modelSpecL, modelSpecR, datasetOptions) {
3865
+ this.modelSpecL = modelSpecL;
3866
+ this.modelSpecR = modelSpecR;
3497
3867
  this.datasetOptions = datasetOptions;
3498
3868
  const renamedDatasetKeys = datasetOptions == null ? void 0 : datasetOptions.renamedDatasetKeys;
3499
3869
  const invertedRenamedKeys = /* @__PURE__ */ new Map();
@@ -3552,7 +3922,47 @@ var ComparisonDatasetsImpl = class {
3552
3922
  }
3553
3923
  }
3554
3924
  }
3925
+ // from ComparisonDatasets interface
3926
+ getReferencePlotsForDataset(datasetKey, scenario) {
3927
+ var _a;
3928
+ if (((_a = this.datasetOptions) == null ? void 0 : _a.referencePlotsForDataset) !== void 0) {
3929
+ const dataset = this.getDataset(datasetKey);
3930
+ if (dataset !== void 0) {
3931
+ return this.datasetOptions.referencePlotsForDataset(dataset, scenario);
3932
+ }
3933
+ }
3934
+ return [];
3935
+ }
3936
+ // from ComparisonDatasets interface
3937
+ getContextGraphIdsForDataset(datasetKey, scenario) {
3938
+ var _a;
3939
+ const dataset = this.getDataset(datasetKey);
3940
+ if (dataset === void 0) {
3941
+ return [];
3942
+ }
3943
+ if (((_a = this.datasetOptions) == null ? void 0 : _a.contextGraphIdsForDataset) !== void 0) {
3944
+ return this.datasetOptions.contextGraphIdsForDataset(dataset, scenario);
3945
+ } else {
3946
+ return getContextGraphIdsForDataset(this.modelSpecL, this.modelSpecR, dataset);
3947
+ }
3948
+ }
3555
3949
  };
3950
+ function getContextGraphIdsForDataset(modelSpecL, modelSpecR, dataset) {
3951
+ const contextGraphIds = /* @__PURE__ */ new Set();
3952
+ function addGraphs(modelSpec, outputVar) {
3953
+ for (const graphSpec of modelSpec.graphSpecs || []) {
3954
+ for (const graphDatasetSpec of graphSpec.datasets) {
3955
+ if (graphDatasetSpec.datasetKey === (outputVar == null ? void 0 : outputVar.datasetKey)) {
3956
+ contextGraphIds.add(graphSpec.id);
3957
+ break;
3958
+ }
3959
+ }
3960
+ }
3961
+ }
3962
+ addGraphs(modelSpecL, dataset.outputVarL);
3963
+ addGraphs(modelSpecR, dataset.outputVarR);
3964
+ return [...contextGraphIds];
3965
+ }
3556
3966
 
3557
3967
  // src/comparison/config/comparison-scenarios.ts
3558
3968
  function getComparisonScenarios(scenarios) {
@@ -3679,9 +4089,7 @@ function createConfig(options) {
3679
4089
  });
3680
4090
  const modelSpecL = baselineBundle.model.modelSpec;
3681
4091
  const modelSpecR = currentBundle.model.modelSpec;
3682
- const modelInputsL = new ModelInputs(modelSpecL);
3683
- const modelInputsR = new ModelInputs(modelSpecR);
3684
- const comparisonDefs = resolveComparisonSpecsFromSources(modelInputsL, modelInputsR, options.comparison.specs);
4092
+ const comparisonDefs = resolveComparisonSpecsFromSources(modelSpecL, modelSpecR, options.comparison.specs);
3685
4093
  comparisonConfig = {
3686
4094
  bundleL: baselineBundle,
3687
4095
  bundleR: currentBundle,
@@ -4293,6 +4701,7 @@ export {
4293
4701
  datasetMessage,
4294
4702
  diffDatasets,
4295
4703
  diffGraphs,
4704
+ getScoresForTestSummaries,
4296
4705
  predicateMessage,
4297
4706
  runSuite,
4298
4707
  scenarioMessage,