@sdeverywhere/check-core 0.1.0 → 0.1.2

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
@@ -17,84 +17,38 @@ var __spreadValues = (a, b) => {
17
17
  return a;
18
18
  };
19
19
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
-
21
- // src/_shared/scenario.ts
22
- import { assertNever } from "assert-never";
23
- function positionSetting(inputVarId, position) {
24
- return {
25
- kind: "position",
26
- inputVarId,
27
- position
28
- };
29
- }
30
- function valueSetting(inputVarId, value) {
31
- return {
32
- kind: "value",
33
- inputVarId,
34
- value
35
- };
36
- }
37
- function settingsScenario(key, groupKey, settings) {
38
- return {
39
- kind: "settings",
40
- key,
41
- groupKey,
42
- settings
43
- };
44
- }
45
- function inputAtPositionScenario(inputVarId, groupKey, position) {
46
- const key = keyForInputAtPosition(`input${inputVarId}`, position);
47
- return settingsScenario(key, groupKey, [positionSetting(inputVarId, position)]);
48
- }
49
- function inputAtValueScenario(inputVarId, groupKey, value) {
50
- const key = keyForInputAtValue(`input${inputVarId}`, value);
51
- return settingsScenario(key, groupKey, [valueSetting(inputVarId, value)]);
52
- }
53
- function allInputsAtPositionScenario(position) {
54
- return {
55
- kind: "all-inputs",
56
- key: keyForInputAtPosition("all_inputs", position),
57
- groupKey: "all_inputs",
58
- position
59
- };
60
- }
61
- function matrixScenarios(inputVarIds) {
62
- const scenarios = [];
63
- scenarios.push(allInputsAtPositionScenario("at-default"));
64
- scenarios.push(allInputsAtPositionScenario("at-minimum"));
65
- scenarios.push(allInputsAtPositionScenario("at-maximum"));
66
- for (const inputVarId of inputVarIds) {
67
- scenarios.push(inputAtPositionScenario(inputVarId, inputVarId, "at-minimum"));
68
- scenarios.push(inputAtPositionScenario(inputVarId, inputVarId, "at-maximum"));
69
- }
70
- return scenarios;
71
- }
72
- function keyForInputPosition(position) {
73
- switch (position) {
74
- case "at-default":
75
- return "default";
76
- case "at-minimum":
77
- return "min";
78
- case "at-maximum":
79
- return "max";
80
- default:
81
- assertNever(position);
82
- }
83
- }
84
- function keyForInputAtPosition(inputKey, position) {
85
- return `${inputKey}_at_${keyForInputPosition(position)}`;
86
- }
87
- function keyForInputAtValue(inputKey, value) {
88
- return `${inputKey}_at_${value}`;
89
- }
20
+ var __async = (__this, __arguments, generator) => {
21
+ return new Promise((resolve, reject) => {
22
+ var fulfilled = (value) => {
23
+ try {
24
+ step(generator.next(value));
25
+ } catch (e) {
26
+ reject(e);
27
+ }
28
+ };
29
+ var rejected = (value) => {
30
+ try {
31
+ step(generator.throw(value));
32
+ } catch (e) {
33
+ reject(e);
34
+ }
35
+ };
36
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
37
+ step((generator = generator.apply(__this, __arguments)).next());
38
+ });
39
+ };
90
40
 
91
41
  // src/_shared/task-queue.ts
92
42
  var TaskQueue = class {
93
43
  constructor(processor) {
94
44
  this.processor = processor;
45
+ /** The queue of task keys, most recent at front. */
95
46
  this.taskKeyQueue = [];
47
+ /** The map of tasks. */
96
48
  this.taskMap = /* @__PURE__ */ new Map();
49
+ /** Whether tasks are being processed. */
97
50
  this.processing = false;
51
+ /** Whether `shutdown` has been called. */
98
52
  this.stopped = false;
99
53
  }
100
54
  addTask(key, input, onComplete) {
@@ -132,39 +86,41 @@ var TaskQueue = class {
132
86
  });
133
87
  }
134
88
  }
135
- async processNextTask() {
136
- var _a, _b;
137
- const taskKey = this.taskKeyQueue.shift();
138
- if (!taskKey) {
139
- return;
140
- }
141
- const task = this.taskMap.get(taskKey);
142
- if (task) {
143
- this.taskMap.delete(taskKey);
144
- } else {
145
- return;
146
- }
147
- let output;
148
- try {
149
- output = await this.processor.process(task.input);
150
- } catch (e) {
151
- if (!this.stopped) {
152
- this.shutdown();
153
- (_a = this.onIdle) == null ? void 0 : _a.call(this, e);
89
+ processNextTask() {
90
+ return __async(this, null, function* () {
91
+ var _a, _b;
92
+ const taskKey = this.taskKeyQueue.shift();
93
+ if (!taskKey) {
94
+ return;
154
95
  }
155
- return;
156
- }
157
- task.onComplete(output);
158
- if (this.taskKeyQueue.length > 0) {
159
- setTimeout(() => {
160
- this.processNextTask();
161
- });
162
- } else {
163
- this.processing = false;
164
- if (!this.stopped) {
165
- (_b = this.onIdle) == null ? void 0 : _b.call(this);
96
+ const task = this.taskMap.get(taskKey);
97
+ if (task) {
98
+ this.taskMap.delete(taskKey);
99
+ } else {
100
+ return;
166
101
  }
167
- }
102
+ let output;
103
+ try {
104
+ output = yield this.processor.process(task.input);
105
+ } catch (e) {
106
+ if (!this.stopped) {
107
+ this.shutdown();
108
+ (_a = this.onIdle) == null ? void 0 : _a.call(this, e);
109
+ }
110
+ return;
111
+ }
112
+ task.onComplete(output);
113
+ if (this.taskKeyQueue.length > 0) {
114
+ setTimeout(() => {
115
+ this.processNextTask();
116
+ });
117
+ } else {
118
+ this.processing = false;
119
+ if (!this.stopped) {
120
+ (_b = this.onIdle) == null ? void 0 : _b.call(this);
121
+ }
122
+ }
123
+ });
168
124
  }
169
125
  };
170
126
 
@@ -173,18 +129,18 @@ var CheckDataCoordinator = class {
173
129
  constructor(bundleModel) {
174
130
  this.bundleModel = bundleModel;
175
131
  this.taskQueue = new TaskQueue({
176
- process: async (request) => {
177
- const result = await this.bundleModel.getDatasetsForScenario(request.scenario, [request.datasetKey]);
132
+ process: (request) => __async(this, null, function* () {
133
+ const result = yield this.bundleModel.getDatasetsForScenario(request.scenarioSpec, [request.datasetKey]);
178
134
  const dataset = result.datasetMap.get(request.datasetKey);
179
135
  return {
180
136
  dataset
181
137
  };
182
- }
138
+ })
183
139
  });
184
140
  }
185
- requestDataset(requestKey, scenario, datasetKey, onResponse) {
141
+ requestDataset(requestKey, scenarioSpec, datasetKey, onResponse) {
186
142
  const request = {
187
- scenario,
143
+ scenarioSpec,
188
144
  datasetKey
189
145
  };
190
146
  this.taskQueue.addTask(requestKey, request, (response) => {
@@ -197,10 +153,10 @@ var CheckDataCoordinator = class {
197
153
  };
198
154
 
199
155
  // src/check/check-report.ts
200
- import assertNever3 from "assert-never";
156
+ import assertNever2 from "assert-never";
201
157
 
202
158
  // src/check/check-predicate.ts
203
- import assertNever2 from "assert-never";
159
+ import assertNever from "assert-never";
204
160
  function symbolForPredicateOp(op) {
205
161
  switch (op) {
206
162
  case "gt":
@@ -216,7 +172,7 @@ function symbolForPredicateOp(op) {
216
172
  case "approx":
217
173
  return "\u2248";
218
174
  default:
219
- assertNever2(op);
175
+ assertNever(op);
220
176
  }
221
177
  }
222
178
 
@@ -230,7 +186,7 @@ function buildCheckReport(checkPlan, checkResults) {
230
186
  const scenarioReports = [];
231
187
  for (const scenarioPlan of testPlan.scenarios) {
232
188
  let scenarioStatus = "passed";
233
- if (scenarioPlan.checkScenario.scenario === void 0) {
189
+ if (scenarioPlan.checkScenario.spec === void 0) {
234
190
  testStatus = "error";
235
191
  scenarioStatus = "error";
236
192
  }
@@ -327,14 +283,14 @@ function predicateReport(predicatePlan, checkKey, result) {
327
283
  };
328
284
  opRef = opDataRef;
329
285
  opValue = `${sym} '${dataRef.dataset.name}'`;
330
- const refScenario = (_b = dataRef.scenario) == null ? void 0 : _b.scenario;
331
- if (!refScenario) {
286
+ const refScenarioSpec = (_b = dataRef.scenario) == null ? void 0 : _b.spec;
287
+ if (!refScenarioSpec) {
332
288
  return;
333
289
  }
334
290
  if (predOp.scenario === "inherit") {
335
291
  opValue += ` (w/ same scenario)`;
336
292
  } else {
337
- if (refScenario.kind === "all-inputs" && refScenario.position === "at-default") {
293
+ if (refScenarioSpec.kind === "all-inputs" && refScenarioSpec.position === "at-default") {
338
294
  opValue += ` (w/ default scenario)`;
339
295
  } else {
340
296
  opValue += ` (w/ configured scenario)`;
@@ -369,7 +325,7 @@ function predicateReport(predicatePlan, checkKey, result) {
369
325
  }
370
326
  function scenarioMessage(scenario, bold) {
371
327
  const checkScenario = scenario.checkScenario;
372
- if (checkScenario.scenario === void 0) {
328
+ if (checkScenario.spec === void 0) {
373
329
  if (checkScenario.error) {
374
330
  switch (checkScenario.error.kind) {
375
331
  case "unknown-input-group":
@@ -377,7 +333,7 @@ function scenarioMessage(scenario, bold) {
377
333
  case "empty-input-group":
378
334
  return `error: input group ${bold(checkScenario.error.name)} is empty`;
379
335
  default:
380
- assertNever3(checkScenario.error.kind);
336
+ assertNever2(checkScenario.error.kind);
381
337
  }
382
338
  } else {
383
339
  const badInputNames = checkScenario.inputDescs.filter((d) => d.inputVar === void 0).map((d) => bold(d.name));
@@ -394,7 +350,7 @@ function scenarioMessage(scenario, bold) {
394
350
  case "at-maximum":
395
351
  return "maximum";
396
352
  default:
397
- assertNever3(position);
353
+ assertNever2(position);
398
354
  }
399
355
  }
400
356
  function inputMessage(inputDesc) {
@@ -409,13 +365,13 @@ function scenarioMessage(scenario, bold) {
409
365
  }
410
366
  return msg;
411
367
  }
412
- if (checkScenario.scenario.kind === "all-inputs") {
413
- const position = checkScenario.scenario.position;
368
+ if (checkScenario.spec.kind === "all-inputs") {
369
+ const position = checkScenario.spec.position;
414
370
  return `when ${bold("all inputs")} are at ${bold(positionName(position))}...`;
415
371
  } else if (checkScenario.inputGroupName) {
416
372
  let position = "at-default";
417
- if (checkScenario.scenario.settings[0].kind === "position") {
418
- position = checkScenario.scenario.settings[0].position;
373
+ if (checkScenario.spec.settings[0].kind === "position") {
374
+ position = checkScenario.spec.settings[0].position;
419
375
  }
420
376
  const groupName = checkScenario.inputGroupName;
421
377
  return `when all inputs in ${bold(groupName)} are at ${bold(positionName(position))}...`;
@@ -448,7 +404,7 @@ function predicateMessage(predicate, bold) {
448
404
  case "empty-input-group":
449
405
  return `error: referenced input group ${bold(result.errorInfo.name)} is empty`;
450
406
  default:
451
- assertNever3(result.errorInfo.kind);
407
+ assertNever2(result.errorInfo.kind);
452
408
  }
453
409
  } else {
454
410
  return `unknown error`;
@@ -1019,7 +975,7 @@ function parseTestYaml(yamlStrings) {
1019
975
  groups.push(group);
1020
976
  }
1021
977
  } else {
1022
- let msg = "Failed to parse YAML tests";
978
+ let msg = "Failed to parse YAML check definitions";
1023
979
  for (const error of validate.errors || []) {
1024
980
  if (error.message) {
1025
981
  msg += `
@@ -1193,9 +1149,12 @@ function actionForPredicate(predicateSpec) {
1193
1149
 
1194
1150
  // src/_shared/combo.ts
1195
1151
  function cartesianProductOf(arr) {
1196
- return arr.reduce((a, b) => {
1197
- return a.map((x) => b.map((y) => x.concat([y]))).reduce((v, w) => v.concat(w), []);
1198
- }, [[]]);
1152
+ return arr.reduce(
1153
+ (a, b) => {
1154
+ return a.map((x) => b.map((y) => x.concat([y]))).reduce((v, w) => v.concat(w), []);
1155
+ },
1156
+ [[]]
1157
+ );
1199
1158
  }
1200
1159
 
1201
1160
  // src/check/check-dataset.ts
@@ -1286,14 +1245,16 @@ function matchByName(modelSpec, datasetName, datasetSource) {
1286
1245
  };
1287
1246
  }
1288
1247
  function matchByGroup(modelSpec, groupName) {
1289
- const groupToMatch = groupName.toLowerCase();
1290
1248
  let matchedGroupName;
1291
1249
  let matchedGroupDatasetKeys;
1292
- for (const [group, datasetKeys] of modelSpec.datasetGroups) {
1293
- if (group.toLowerCase() === groupToMatch) {
1294
- matchedGroupName = group;
1295
- matchedGroupDatasetKeys = datasetKeys;
1296
- break;
1250
+ if (modelSpec.datasetGroups) {
1251
+ const groupToMatch = groupName.toLowerCase();
1252
+ for (const [group, datasetKeys] of modelSpec.datasetGroups) {
1253
+ if (group.toLowerCase() === groupToMatch) {
1254
+ matchedGroupName = group;
1255
+ matchedGroupDatasetKeys = datasetKeys;
1256
+ break;
1257
+ }
1297
1258
  }
1298
1259
  }
1299
1260
  if (matchedGroupName === void 0) {
@@ -1370,6 +1331,65 @@ function matchByType(modelSpec, varTypeToMatch) {
1370
1331
 
1371
1332
  // src/check/check-scenario.ts
1372
1333
  import assertNever4 from "assert-never";
1334
+
1335
+ // src/_shared/scenario-specs.ts
1336
+ import { assertNever as assertNever3 } from "assert-never";
1337
+ function positionSetting(inputVarId, position) {
1338
+ return {
1339
+ kind: "position",
1340
+ inputVarId,
1341
+ position
1342
+ };
1343
+ }
1344
+ function valueSetting(inputVarId, value) {
1345
+ return {
1346
+ kind: "value",
1347
+ inputVarId,
1348
+ value
1349
+ };
1350
+ }
1351
+ function inputSettingsSpec(settings) {
1352
+ const uidParts = settings.map((setting) => {
1353
+ switch (setting.kind) {
1354
+ case "position":
1355
+ return `${setting.inputVarId}_at_${keyForInputPosition(setting.position)}`;
1356
+ case "value":
1357
+ return `${setting.inputVarId}_at_${setting.value}`;
1358
+ default:
1359
+ assertNever3(setting);
1360
+ }
1361
+ });
1362
+ const uid = `inputs_${uidParts.sort().join("_")}`;
1363
+ return {
1364
+ kind: "input-settings",
1365
+ uid,
1366
+ settings
1367
+ };
1368
+ }
1369
+ function inputAtPositionSpec(inputVarId, position) {
1370
+ return inputSettingsSpec([positionSetting(inputVarId, position)]);
1371
+ }
1372
+ function allInputsAtPositionSpec(position) {
1373
+ return {
1374
+ kind: "all-inputs",
1375
+ uid: `all_inputs_at_${keyForInputPosition(position)}`,
1376
+ position
1377
+ };
1378
+ }
1379
+ function keyForInputPosition(position) {
1380
+ switch (position) {
1381
+ case "at-default":
1382
+ return "default";
1383
+ case "at-minimum":
1384
+ return "min";
1385
+ case "at-maximum":
1386
+ return "max";
1387
+ default:
1388
+ assertNever3(position);
1389
+ }
1390
+ }
1391
+
1392
+ // src/check/check-scenario.ts
1373
1393
  function expandScenarios(modelSpec, scenarioSpecs, simplify) {
1374
1394
  if (scenarioSpecs.length === 0) {
1375
1395
  const scenarioSpec = {
@@ -1446,10 +1466,12 @@ function inputDescForName(modelSpec, inputName, at) {
1446
1466
  }
1447
1467
  }
1448
1468
  function groupForName(modelSpec, groupName) {
1449
- const groupToMatch = groupName.toLowerCase();
1450
- for (const [group, inputVars] of modelSpec.inputGroups) {
1451
- if (group.toLowerCase() === groupToMatch) {
1452
- return [group, inputVars];
1469
+ if (modelSpec.inputGroups) {
1470
+ const groupToMatch = groupName.toLowerCase();
1471
+ for (const [group, inputVars] of modelSpec.inputGroups) {
1472
+ if (group.toLowerCase() === groupToMatch) {
1473
+ return [group, inputVars];
1474
+ }
1453
1475
  }
1454
1476
  }
1455
1477
  return void 0;
@@ -1464,56 +1486,35 @@ function errorScenarioForInputGroup(kind, groupName) {
1464
1486
  };
1465
1487
  }
1466
1488
  function checkScenarioWithAllInputsAtPosition(position) {
1467
- const scenario = allInputsAtPositionScenario(position);
1468
1489
  return {
1469
- scenario,
1490
+ spec: allInputsAtPositionSpec(position),
1470
1491
  inputDescs: []
1471
1492
  };
1472
1493
  }
1473
1494
  function checkScenarioWithInputAtPosition(inputVar, position) {
1474
1495
  const varId = inputVar.varId;
1475
- const scenario = inputAtPositionScenario(varId, varId, position);
1476
1496
  return {
1477
- scenario,
1497
+ spec: inputAtPositionSpec(varId, position),
1478
1498
  inputDescs: [inputDescAtPosition(inputVar, position)]
1479
1499
  };
1480
1500
  }
1481
1501
  function checkScenarioForInputDescs(groupName, inputDescs) {
1482
- let scenario;
1502
+ let spec;
1483
1503
  if (inputDescs.every((desc) => desc.inputVar !== void 0)) {
1484
- const settings = [];
1485
- const keyParts = [];
1486
- for (const inputDesc of inputDescs) {
1504
+ const settings = inputDescs.map((inputDesc) => {
1487
1505
  const varId = inputDesc.inputVar.varId;
1488
1506
  if (inputDesc.position) {
1489
- settings.push(positionSetting(varId, inputDesc.position));
1490
- keyParts.push(keyForInputAtPosition(varId, inputDesc.position));
1491
- } else {
1492
- settings.push(valueSetting(varId, inputDesc.value));
1493
- keyParts.push(keyForInputAtValue(varId, inputDesc.value));
1494
- }
1495
- }
1496
- if (settings.length === 1) {
1497
- const scenarioKey = `input${keyParts[0]}`;
1498
- const groupKey = settings[0].inputVarId;
1499
- scenario = settingsScenario(scenarioKey, groupKey, settings);
1500
- } else if (settings.length > 1) {
1501
- let scenarioKey;
1502
- let groupKey;
1503
- if (groupName) {
1504
- scenarioKey = `group_${groupName.toLowerCase().replace(/ /g, "_")}`;
1505
- groupKey = scenarioKey;
1507
+ return positionSetting(varId, inputDesc.position);
1506
1508
  } else {
1507
- scenarioKey = "multi" + keyParts.join("_");
1508
- groupKey = scenarioKey;
1509
+ return valueSetting(varId, inputDesc.value);
1509
1510
  }
1510
- scenario = settingsScenario(scenarioKey, groupKey, settings);
1511
- }
1511
+ });
1512
+ spec = inputSettingsSpec(settings);
1512
1513
  } else {
1513
- scenario = void 0;
1514
+ spec = void 0;
1514
1515
  }
1515
1516
  return {
1516
- scenario,
1517
+ spec,
1517
1518
  inputGroupName: groupName,
1518
1519
  inputDescs
1519
1520
  };
@@ -1627,7 +1628,7 @@ var CheckPlanner = class {
1627
1628
  }
1628
1629
  const planScenarios = [];
1629
1630
  for (const checkScenario of checkScenarios) {
1630
- if (checkScenario.scenario === void 0) {
1631
+ if (checkScenario.spec === void 0) {
1631
1632
  planScenarios.push({
1632
1633
  checkScenario,
1633
1634
  datasets: []
@@ -1687,6 +1688,24 @@ var CheckPlanner = class {
1687
1688
  dataRefs: this.dataRefs
1688
1689
  };
1689
1690
  }
1691
+ /**
1692
+ * Record any references to additional datasets contained in the given predicate.
1693
+ * For example, if the predicate is:
1694
+ * ```
1695
+ * gt:
1696
+ * dataset:
1697
+ * name: 'XYZ'
1698
+ * scenario:
1699
+ * inputs: all
1700
+ * at: default
1701
+ * ```
1702
+ * this will add a reference to the scenario/dataset pair so that the data can
1703
+ * be fetched in a later stage.
1704
+ *
1705
+ * @param predicateSpec The predicate spec.
1706
+ * @param checkScenario The scenario in which the dataset is being checked.
1707
+ * @param checkDataset The dataset that is being checked.
1708
+ */
1690
1709
  addDataRefs(predicateSpec, checkScenario, checkDataset) {
1691
1710
  let dataRefs;
1692
1711
  const addDataRef = (op) => {
@@ -1736,8 +1755,8 @@ var CheckPlanner = class {
1736
1755
  }
1737
1756
  }
1738
1757
  let dataRefKey;
1739
- if (refScenario.scenario && refDataset.datasetKey) {
1740
- dataRefKey = `${refScenario.scenario.key}::${refDataset.datasetKey}`;
1758
+ if (refScenario.spec && refDataset.datasetKey) {
1759
+ dataRefKey = `${refScenario.spec.uid}::${refDataset.datasetKey}`;
1741
1760
  }
1742
1761
  const dataRef = {
1743
1762
  key: dataRefKey,
@@ -1792,14 +1811,14 @@ function checkSummaryFromReport(checkReport) {
1792
1811
  predicateSummaries
1793
1812
  };
1794
1813
  }
1795
- function checkReportFromSummary(checkConfig, checkSummary, simplifyScenarios) {
1814
+ function checkReportFromSummary(checkConfig, checkSummary) {
1796
1815
  const checkSpecResult = parseTestYaml(checkConfig.tests);
1797
1816
  if (checkSpecResult.isErr()) {
1798
1817
  return void 0;
1799
1818
  }
1800
1819
  const checkSpec = checkSpecResult.value;
1801
1820
  const checkPlanner = new CheckPlanner(checkConfig.bundle.model.modelSpec);
1802
- checkPlanner.addAllChecks(checkSpec, simplifyScenarios);
1821
+ checkPlanner.addAllChecks(checkSpec, false);
1803
1822
  const checkPlan = checkPlanner.buildPlan();
1804
1823
  const checkResults = /* @__PURE__ */ new Map();
1805
1824
  for (const predicateSummary of checkSummary.predicateSummaries) {
@@ -1808,44 +1827,74 @@ function checkReportFromSummary(checkConfig, checkSummary, simplifyScenarios) {
1808
1827
  return buildCheckReport(checkPlan, checkResults);
1809
1828
  }
1810
1829
 
1811
- // src/compare/compare-data-coordinator.ts
1830
+ // src/comparison/run/comparison-data-coordinator.ts
1812
1831
  import { assertNever as assertNever7 } from "assert-never";
1813
- var CompareDataCoordinator = class {
1832
+ var ComparisonDataCoordinator = class {
1814
1833
  constructor(bundleModelL, bundleModelR) {
1815
1834
  this.bundleModelL = bundleModelL;
1816
1835
  this.bundleModelR = bundleModelR;
1817
1836
  this.taskQueue = new TaskQueue({
1818
- process: async (request) => {
1837
+ process: (request) => __async(this, null, function* () {
1819
1838
  switch (request.kind) {
1820
- case "dataset": {
1821
- const [resultL, resultR] = await Promise.all([
1822
- this.bundleModelL.getDatasetsForScenario(request.scenario, request.datasetKeys),
1823
- this.bundleModelR.getDatasetsForScenario(request.scenario, request.datasetKeys)
1824
- ]);
1825
- return {
1826
- kind: "dataset",
1827
- datasetMapL: resultL.datasetMap,
1828
- datasetMapR: resultR.datasetMap
1829
- };
1830
- }
1831
- case "graph-data": {
1832
- const bundleModel = request.bundle === "right" ? this.bundleModelR : this.bundleModelL;
1833
- const graphData = await bundleModel.getGraphDataForScenario(request.scenario, request.graphId);
1834
- return {
1835
- kind: "graph-data",
1836
- graphData
1837
- };
1838
- }
1839
+ case "dataset":
1840
+ return this.processDatasetRequest(request);
1841
+ case "graph-data":
1842
+ return this.processGraphDataRequest(request);
1839
1843
  default:
1840
1844
  assertNever7(request);
1841
1845
  }
1846
+ })
1847
+ });
1848
+ }
1849
+ processDatasetRequest(request) {
1850
+ return __async(this, null, function* () {
1851
+ function fetchDatasets(bundleModel, scenarioSpec) {
1852
+ return __async(this, null, function* () {
1853
+ if (scenarioSpec) {
1854
+ return bundleModel.getDatasetsForScenario(scenarioSpec, request.datasetKeys);
1855
+ } else {
1856
+ return void 0;
1857
+ }
1858
+ });
1859
+ }
1860
+ const [resultL, resultR] = yield Promise.all([
1861
+ fetchDatasets(this.bundleModelL, request.scenarioSpecL),
1862
+ fetchDatasets(this.bundleModelR, request.scenarioSpecR)
1863
+ ]);
1864
+ return {
1865
+ kind: "dataset",
1866
+ datasetMapL: resultL == null ? void 0 : resultL.datasetMap,
1867
+ datasetMapR: resultR == null ? void 0 : resultR.datasetMap
1868
+ };
1869
+ });
1870
+ }
1871
+ processGraphDataRequest(request) {
1872
+ return __async(this, null, function* () {
1873
+ function fetchGraphData(bundleModel, scenarioSpec) {
1874
+ return __async(this, null, function* () {
1875
+ if (scenarioSpec) {
1876
+ return bundleModel.getGraphDataForScenario(scenarioSpec, request.graphId);
1877
+ } else {
1878
+ return void 0;
1879
+ }
1880
+ });
1842
1881
  }
1882
+ const [graphDataL, graphDataR] = yield Promise.all([
1883
+ fetchGraphData(this.bundleModelL, request.scenarioSpecL),
1884
+ fetchGraphData(this.bundleModelR, request.scenarioSpecR)
1885
+ ]);
1886
+ return {
1887
+ kind: "graph-data",
1888
+ graphDataL,
1889
+ graphDataR
1890
+ };
1843
1891
  });
1844
1892
  }
1845
- requestDatasetMaps(requestKey, scenario, datasetKeys, onResponse) {
1893
+ requestDatasetMaps(requestKey, scenarioSpecL, scenarioSpecR, datasetKeys, onResponse) {
1846
1894
  const request = {
1847
1895
  kind: "dataset",
1848
- scenario,
1896
+ scenarioSpecL,
1897
+ scenarioSpecR,
1849
1898
  datasetKeys
1850
1899
  };
1851
1900
  this.taskQueue.addTask(requestKey, request, (response) => {
@@ -1854,16 +1903,16 @@ var CompareDataCoordinator = class {
1854
1903
  }
1855
1904
  });
1856
1905
  }
1857
- requestGraphData(requestKey, bundle, scenario, graphId, onResponse) {
1906
+ requestGraphData(requestKey, scenarioSpecL, scenarioSpecR, graphId, onResponse) {
1858
1907
  const request = {
1859
1908
  kind: "graph-data",
1860
- bundle,
1861
- scenario,
1909
+ scenarioSpecL,
1910
+ scenarioSpecR,
1862
1911
  graphId
1863
1912
  };
1864
1913
  this.taskQueue.addTask(requestKey, request, (response) => {
1865
1914
  if (response.kind === "graph-data") {
1866
- onResponse(response.graphData);
1915
+ onResponse(response.graphDataL, response.graphDataR);
1867
1916
  }
1868
1917
  });
1869
1918
  }
@@ -1872,7 +1921,7 @@ var CompareDataCoordinator = class {
1872
1921
  }
1873
1922
  };
1874
1923
 
1875
- // src/compare/compare-datasets.ts
1924
+ // src/comparison/diff-datasets/diff-datasets.ts
1876
1925
  function diffDatasets(datasetL, datasetR) {
1877
1926
  let minValueL = Number.MAX_VALUE;
1878
1927
  let maxValueL = Number.MIN_VALUE;
@@ -1967,19 +2016,9 @@ function diffDatasets(datasetL, datasetR) {
1967
2016
  maxDiffPoint
1968
2017
  };
1969
2018
  }
1970
- function compareDatasets(scenarioKey, datasetKey, datasetMapL, datasetMapR) {
1971
- const datasetL = datasetMapL.get(datasetKey);
1972
- const datasetR = datasetMapR.get(datasetKey);
1973
- const diffReport = diffDatasets(datasetL, datasetR);
1974
- return {
1975
- scenarioKey,
1976
- datasetKey,
1977
- diffReport
1978
- };
1979
- }
1980
2019
 
1981
- // src/compare/compare-graphs.ts
1982
- function diffGraphs(graphL, graphR, scenarioKey, datasetSummaries) {
2020
+ // src/comparison/diff-graphs/diff-graphs.ts
2021
+ function diffGraphs(graphL, graphR, scenarioKey, testSummaries) {
1983
2022
  let inclusion;
1984
2023
  if (graphL && graphR) {
1985
2024
  inclusion = "both";
@@ -2021,8 +2060,8 @@ function diffGraphs(graphL, graphR, scenarioKey, datasetSummaries) {
2021
2060
  datasetKeys.add(dataset.datasetKey);
2022
2061
  }
2023
2062
  for (const datasetKey of datasetKeys) {
2024
- const summary = datasetSummaries.find((summary2) => summary2.d === datasetKey && summary2.s === scenarioKey);
2025
- const maxDiff = summary !== void 0 ? summary.md : void 0;
2063
+ const testSummary = testSummaries.find((summary) => summary.d === datasetKey && summary.s === scenarioKey);
2064
+ const maxDiff = testSummary == null ? void 0 : testSummary.md;
2026
2065
  datasetReports.push({
2027
2066
  datasetKey,
2028
2067
  maxDiff
@@ -2036,12 +2075,12 @@ function diffGraphs(graphL, graphR, scenarioKey, datasetSummaries) {
2036
2075
  };
2037
2076
  }
2038
2077
 
2039
- // src/compare/compare-summary.ts
2040
- function compareSummaryFromReport(compareReport) {
2041
- const datasetSummaries = [];
2042
- for (const r of compareReport.datasetReports) {
2078
+ // src/comparison/report/comparison-reporting.ts
2079
+ function comparisonSummaryFromReport(comparisonReport) {
2080
+ const terseSummaries = [];
2081
+ for (const r of comparisonReport.testReports) {
2043
2082
  if (r.diffReport.validity === "both" && r.diffReport.maxDiff > 0) {
2044
- datasetSummaries.push({
2083
+ terseSummaries.push({
2045
2084
  s: r.scenarioKey,
2046
2085
  d: r.datasetKey,
2047
2086
  md: r.diffReport.maxDiff
@@ -2049,394 +2088,1633 @@ function compareSummaryFromReport(compareReport) {
2049
2088
  }
2050
2089
  }
2051
2090
  return {
2052
- datasetSummaries,
2053
- perfReportL: compareReport.perfReportL,
2054
- perfReportR: compareReport.perfReportR
2091
+ testSummaries: terseSummaries,
2092
+ perfReportL: comparisonReport.perfReportL,
2093
+ perfReportR: comparisonReport.perfReportR
2055
2094
  };
2056
2095
  }
2096
+ function restoreFromTerseSummaries(comparisonConfig, terseSummaries) {
2097
+ const existingSummaries = /* @__PURE__ */ new Map();
2098
+ for (const summary of terseSummaries) {
2099
+ const key = `${summary.s}::${summary.d}`;
2100
+ existingSummaries.set(key, summary);
2101
+ }
2102
+ const allTestSummaries = [];
2103
+ for (const scenario of comparisonConfig.scenarios.getAllScenarios()) {
2104
+ const datasetKeys = comparisonConfig.datasets.getDatasetKeysForScenario(scenario);
2105
+ for (const datasetKey of datasetKeys) {
2106
+ const key = `${scenario.key}::${datasetKey}`;
2107
+ const existingSummary = existingSummaries.get(key);
2108
+ const maxDiff = (existingSummary == null ? void 0 : existingSummary.md) || 0;
2109
+ allTestSummaries.push({
2110
+ s: scenario.key,
2111
+ d: datasetKey,
2112
+ md: maxDiff
2113
+ });
2114
+ }
2115
+ }
2116
+ return allTestSummaries;
2117
+ }
2057
2118
 
2058
- // src/config/synchronized-model.ts
2059
- function synchronizedBundleModel(sourceModel) {
2060
- var _a;
2061
- const promiseQueue = new PromiseQueue();
2119
+ // src/comparison/report/comparison-grouping.ts
2120
+ import { assertNever as assertNever8 } from "assert-never";
2121
+
2122
+ // src/comparison/report/buckets.ts
2123
+ function getBucketIndex(diffPct, thresholds) {
2124
+ if (diffPct === 0) {
2125
+ return 0;
2126
+ }
2127
+ for (let i = 0; i < thresholds.length; i++) {
2128
+ if (diffPct < thresholds[i]) {
2129
+ return i + 1;
2130
+ }
2131
+ }
2132
+ return thresholds.length + 1;
2133
+ }
2134
+
2135
+ // src/comparison/report/comparison-grouping.ts
2136
+ function categorizeComparisonTestSummaries(comparisonConfig, terseSummaries) {
2137
+ const allTestSummaries = restoreFromTerseSummaries(comparisonConfig, terseSummaries);
2138
+ const groupsByScenario = groupComparisonTestSummaries(allTestSummaries, "by-scenario");
2139
+ const byScenario = categorizeComparisonGroups(comparisonConfig, [...groupsByScenario.values()]);
2140
+ const groupsByDataset = groupComparisonTestSummaries(allTestSummaries, "by-dataset");
2141
+ const byDataset = categorizeComparisonGroups(comparisonConfig, [...groupsByDataset.values()]);
2062
2142
  return {
2063
- modelSpec: sourceModel.modelSpec,
2064
- getDatasetsForScenario: (scenario, datasetKeys) => {
2065
- return promiseQueue.add(() => sourceModel.getDatasetsForScenario(scenario, datasetKeys));
2066
- },
2067
- getGraphsForDataset: (_a = sourceModel.getGraphsForDataset) == null ? void 0 : _a.bind(sourceModel),
2068
- getGraphDataForScenario: (scenario, graphId) => {
2069
- return promiseQueue.add(() => sourceModel.getGraphDataForScenario(scenario, graphId));
2070
- },
2071
- getGraphLinksForScenario: sourceModel.getGraphLinksForScenario.bind(sourceModel)
2143
+ byScenario,
2144
+ byDataset
2072
2145
  };
2073
2146
  }
2074
- var PromiseQueue = class {
2075
- constructor() {
2076
- this.tasks = [];
2077
- this.runningCount = 0;
2147
+ function groupComparisonTestSummaries(testSummaries, groupKind) {
2148
+ const groups = /* @__PURE__ */ new Map();
2149
+ for (const testSummary of testSummaries) {
2150
+ let groupKey;
2151
+ switch (groupKind) {
2152
+ case "by-dataset":
2153
+ groupKey = testSummary.d;
2154
+ break;
2155
+ case "by-scenario":
2156
+ groupKey = testSummary.s;
2157
+ break;
2158
+ default:
2159
+ assertNever8(groupKind);
2160
+ }
2161
+ const group = groups.get(groupKey);
2162
+ if (group) {
2163
+ group.testSummaries.push(testSummary);
2164
+ } else {
2165
+ groups.set(groupKey, {
2166
+ kind: groupKind,
2167
+ key: groupKey,
2168
+ testSummaries: [testSummary]
2169
+ });
2170
+ }
2078
2171
  }
2079
- add(f) {
2080
- return new Promise((resolve, reject) => {
2081
- const run = async () => {
2082
- this.runningCount++;
2083
- const promise = f();
2084
- try {
2085
- const result = await promise;
2086
- resolve(result);
2087
- } catch (e) {
2088
- reject(e);
2089
- } finally {
2090
- this.runningCount--;
2091
- this.runNext();
2092
- }
2093
- };
2094
- if (this.runningCount < 1) {
2095
- run();
2172
+ return groups;
2173
+ }
2174
+ function categorizeComparisonGroups(comparisonConfig, allGroups) {
2175
+ const allGroupSummaries = /* @__PURE__ */ new Map();
2176
+ const withErrors = [];
2177
+ const onlyInLeft = [];
2178
+ const onlyInRight = [];
2179
+ let withDiffs = [];
2180
+ const withoutDiffs = [];
2181
+ function addSummaryForGroup(group, root, validInL, validInR) {
2182
+ let scores;
2183
+ if (validInL && validInR) {
2184
+ scores = getScoresForGroup(group, comparisonConfig.thresholds);
2185
+ }
2186
+ const groupSummary = {
2187
+ root,
2188
+ group,
2189
+ scores
2190
+ };
2191
+ allGroupSummaries.set(group.key, groupSummary);
2192
+ if (validInL && validInR) {
2193
+ if (scores.totalDiffCount !== scores.diffCountByBucket[0]) {
2194
+ withDiffs.push(groupSummary);
2096
2195
  } else {
2097
- this.tasks.push(run);
2196
+ withoutDiffs.push(groupSummary);
2098
2197
  }
2099
- });
2198
+ } else if (validInL) {
2199
+ onlyInLeft.push(groupSummary);
2200
+ } else if (validInR) {
2201
+ onlyInRight.push(groupSummary);
2202
+ } else {
2203
+ withErrors.push(groupSummary);
2204
+ }
2100
2205
  }
2101
- runNext() {
2102
- if (this.tasks.length > 0) {
2103
- const task = this.tasks.shift();
2104
- if (task) {
2105
- task();
2206
+ for (const group of allGroups.values()) {
2207
+ switch (group.kind) {
2208
+ case "by-dataset": {
2209
+ const dataset = comparisonConfig.datasets.getDataset(group.key);
2210
+ const validInL = (dataset == null ? void 0 : dataset.outputVarL) !== void 0;
2211
+ const validInR = (dataset == null ? void 0 : dataset.outputVarR) !== void 0;
2212
+ addSummaryForGroup(group, dataset, validInL, validInR);
2213
+ break;
2106
2214
  }
2215
+ case "by-scenario": {
2216
+ const scenario = comparisonConfig.scenarios.getScenario(group.key);
2217
+ const validInL = (scenario == null ? void 0 : scenario.specL) !== void 0;
2218
+ const validInR = (scenario == null ? void 0 : scenario.specR) !== void 0;
2219
+ addSummaryForGroup(group, scenario, validInL, validInR);
2220
+ break;
2221
+ }
2222
+ default:
2223
+ assertNever8(group.kind);
2107
2224
  }
2108
2225
  }
2109
- };
2110
-
2111
- // src/config/config.ts
2112
- async function createConfig(options) {
2113
- var _a;
2114
- const origCurrentBundle = await loadSynchronized(options.current);
2115
- let currentBundle;
2116
- let compareConfig;
2117
- if (options.compare === void 0) {
2118
- currentBundle = origCurrentBundle;
2119
- } else {
2120
- const baselineBundle = await loadSynchronized(options.compare.baseline);
2121
- const renamedDatasetKeys = options.compare.datasets.renamedDatasetKeys;
2122
- const invertedRenamedKeys = /* @__PURE__ */ new Map();
2123
- renamedDatasetKeys == null ? void 0 : renamedDatasetKeys.forEach((newKey, oldKey) => {
2124
- invertedRenamedKeys.set(newKey, oldKey);
2125
- });
2126
- const rightKeyForLeftKey = (leftKey) => {
2127
- return (renamedDatasetKeys == null ? void 0 : renamedDatasetKeys.get(leftKey)) || leftKey;
2128
- };
2129
- const leftKeyForRightKey = (rightKey) => {
2130
- return invertedRenamedKeys.get(rightKey) || rightKey;
2131
- };
2132
- const origBundleModelR = origCurrentBundle.model;
2133
- const adjBundleModelR = {
2134
- modelSpec: origBundleModelR.modelSpec,
2135
- getDatasetsForScenario: async (scenario, datasetKeys) => {
2136
- const rightKeys = datasetKeys.map(rightKeyForLeftKey);
2137
- const result = await origBundleModelR.getDatasetsForScenario(scenario, rightKeys);
2138
- const mapWithRightKeys = result.datasetMap;
2139
- const mapWithLeftKeys = /* @__PURE__ */ new Map();
2140
- for (const [rightKey, dataset] of mapWithRightKeys.entries()) {
2141
- const leftKey = leftKeyForRightKey(rightKey);
2142
- mapWithLeftKeys.set(leftKey, dataset);
2143
- }
2144
- return {
2145
- datasetMap: mapWithLeftKeys,
2146
- modelRunTime: result.modelRunTime
2147
- };
2148
- },
2149
- getGraphsForDataset: (_a = origBundleModelR.getGraphsForDataset) == null ? void 0 : _a.bind(origBundleModelR),
2150
- getGraphDataForScenario: origBundleModelR.getGraphDataForScenario.bind(origBundleModelR),
2151
- getGraphLinksForScenario: origBundleModelR.getGraphLinksForScenario.bind(origBundleModelR)
2152
- };
2153
- currentBundle = __spreadProps(__spreadValues({}, origCurrentBundle), {
2154
- model: adjBundleModelR
2155
- });
2156
- compareConfig = {
2157
- bundleL: baselineBundle,
2158
- bundleR: currentBundle,
2159
- thresholds: options.compare.thresholds,
2160
- scenarios: options.compare.scenarios,
2161
- datasets: options.compare.datasets
2162
- };
2226
+ if (withDiffs.length > 1) {
2227
+ if (withDiffs[0].group.kind === "by-dataset") {
2228
+ withDiffs = sortDatasetGroupSummaries(withDiffs);
2229
+ } else if (withDiffs[0].group.kind === "by-scenario") {
2230
+ withDiffs = sortScenarioGroupSummaries(withDiffs);
2231
+ }
2163
2232
  }
2164
- const checkConfig = {
2165
- bundle: currentBundle,
2166
- tests: options.check.tests
2167
- };
2168
2233
  return {
2169
- check: checkConfig,
2170
- compare: compareConfig
2234
+ allGroupSummaries,
2235
+ withErrors,
2236
+ onlyInLeft,
2237
+ onlyInRight,
2238
+ withDiffs,
2239
+ withoutDiffs
2171
2240
  };
2172
2241
  }
2173
- async function loadSynchronized(sourceBundle) {
2174
- const sourceModel = await sourceBundle.bundle.initModel();
2175
- const synchronizedModel = synchronizedBundleModel(sourceModel);
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
+ }
2176
2258
  return {
2177
- name: sourceBundle.name,
2178
- version: sourceBundle.bundle.version,
2179
- model: synchronizedModel
2259
+ totalDiffCount,
2260
+ totalMaxDiffByBucket,
2261
+ diffCountByBucket,
2262
+ diffPercentByBucket
2180
2263
  };
2181
2264
  }
2182
-
2183
- // src/config/dataset-manager.ts
2184
- var DatasetManager = class {
2185
- constructor(bundleL, bundleR, renamedDatasetKeys) {
2186
- this.bundleL = bundleL;
2187
- this.bundleR = bundleR;
2188
- this.renamedDatasetKeys = renamedDatasetKeys;
2189
- const invertedRenamedKeys = /* @__PURE__ */ new Map();
2190
- renamedDatasetKeys == null ? void 0 : renamedDatasetKeys.forEach((newKey, oldKey) => {
2191
- invertedRenamedKeys.set(newKey, oldKey);
2192
- });
2193
- function leftKeyForRightKey(rightKey) {
2194
- return invertedRenamedKeys.get(rightKey) || rightKey;
2265
+ function sortDatasetGroupSummaries(summaries) {
2266
+ return summaries.sort((a, b) => {
2267
+ var _a, _b;
2268
+ const scoreResult = compareScores(a.scores, b.scores);
2269
+ if (scoreResult !== 0) {
2270
+ return -scoreResult;
2271
+ } else {
2272
+ const aVar = a.root.outputVarL;
2273
+ const bVar = b.root.outputVarR;
2274
+ const aSource = ((_a = aVar.sourceName) == null ? void 0 : _a.toLowerCase()) || "";
2275
+ const bSource = ((_b = bVar.sourceName) == null ? void 0 : _b.toLowerCase()) || "";
2276
+ if (aSource !== bSource) {
2277
+ return aSource.localeCompare(bSource);
2278
+ } else {
2279
+ const aName = aVar.varName.toLowerCase();
2280
+ const bName = bVar.varName.toLowerCase();
2281
+ return aName.localeCompare(bName);
2282
+ }
2195
2283
  }
2196
- const allOutputVarKeysSet = /* @__PURE__ */ new Set();
2197
- const modelOutputVarKeysSet = /* @__PURE__ */ new Set();
2198
- function addOutputVars(outputVars, handleRenames) {
2199
- outputVars.forEach((outputVar, key) => {
2200
- const remappedKey = handleRenames ? leftKeyForRightKey(key) : key;
2201
- allOutputVarKeysSet.add(remappedKey);
2284
+ });
2285
+ }
2286
+ function sortScenarioGroupSummaries(summaries) {
2287
+ return summaries.sort((a, b) => {
2288
+ var _a, _b;
2289
+ const scoreResult = compareScores(a.scores, b.scores);
2290
+ if (scoreResult !== 0) {
2291
+ return -scoreResult;
2292
+ } else {
2293
+ const aScenario = a.root;
2294
+ const bScenario = b.root;
2295
+ const aTitle = aScenario.title.toLowerCase();
2296
+ const bTitle = bScenario.title.toLowerCase();
2297
+ if (aTitle !== bTitle) {
2298
+ return aTitle.localeCompare(bTitle);
2299
+ } else {
2300
+ const aSubtitle = ((_a = aScenario.subtitle) == null ? void 0 : _a.toLowerCase()) || "";
2301
+ const bSubtitle = ((_b = bScenario.subtitle) == null ? void 0 : _b.toLowerCase()) || "";
2302
+ return aSubtitle.localeCompare(bSubtitle);
2303
+ }
2304
+ }
2305
+ });
2306
+ }
2307
+ function compareScores(a, b) {
2308
+ if (a.totalMaxDiffByBucket.length !== b.totalMaxDiffByBucket.length) {
2309
+ return 0;
2310
+ }
2311
+ const len = a.totalMaxDiffByBucket.length;
2312
+ for (let i = len - 1; i >= 0; i--) {
2313
+ const aTotal = a.totalMaxDiffByBucket[i];
2314
+ const bTotal = b.totalMaxDiffByBucket[i];
2315
+ if (aTotal > bTotal) {
2316
+ return 1;
2317
+ } else if (aTotal < bTotal) {
2318
+ return -1;
2319
+ }
2320
+ }
2321
+ return 0;
2322
+ }
2323
+
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
+ // src/comparison/config/parse/comparison-parser.ts
2376
+ import Ajv2 from "ajv";
2377
+ import assertNever9 from "assert-never";
2378
+ import { err as err2, ok as ok2 } from "neverthrow";
2379
+ import yaml2 from "yaml";
2380
+
2381
+ // src/comparison/config/parse/comparison.schema.js
2382
+ var comparison_schema_default = {
2383
+ $schema: "http://json-schema.org/draft-07/schema#",
2384
+ title: "Model Comparison Test",
2385
+ type: "array",
2386
+ description: "A group of model comparison scenarios and views.",
2387
+ items: {
2388
+ $ref: "#/$defs/top_level_array_item"
2389
+ },
2390
+ $defs: {
2391
+ //
2392
+ // TOP-LEVEL
2393
+ //
2394
+ top_level_array_item: {
2395
+ oneOf: [
2396
+ { $ref: "#/$defs/scenario_array_item" },
2397
+ { $ref: "#/$defs/scenario_group_array_item" },
2398
+ { $ref: "#/$defs/view_group_array_item" }
2399
+ ]
2400
+ },
2401
+ //
2402
+ // SCENARIOS
2403
+ //
2404
+ scenario_array_item: {
2405
+ type: "object",
2406
+ additionalProperties: false,
2407
+ properties: {
2408
+ scenario: {
2409
+ $ref: "#/$defs/scenario"
2410
+ }
2411
+ },
2412
+ required: ["scenario"]
2413
+ },
2414
+ scenario: {
2415
+ oneOf: [
2416
+ { $ref: "#/$defs/scenario_with_input_at_position" },
2417
+ { $ref: "#/$defs/scenario_with_input_at_value" },
2418
+ { $ref: "#/$defs/scenario_with_multiple_input_settings" },
2419
+ { $ref: "#/$defs/scenario_with_inputs_in_preset_at_position" },
2420
+ // { $ref: '#/$defs/scenario_with_inputs_in_group_at_position' }
2421
+ { $ref: "#/$defs/scenario_preset" }
2422
+ // { $ref: '#/$defs/scenario_expand_for_each_input_in_group' }
2423
+ ]
2424
+ },
2425
+ scenario_position: {
2426
+ type: "string",
2427
+ enum: ["min", "max", "default"]
2428
+ },
2429
+ scenario_with_input_at_position: {
2430
+ type: "object",
2431
+ additionalProperties: false,
2432
+ properties: {
2433
+ id: {
2434
+ type: "string"
2435
+ },
2436
+ title: {
2437
+ type: "string"
2438
+ },
2439
+ subtitle: {
2440
+ type: "string"
2441
+ },
2442
+ with: {
2443
+ type: "string"
2444
+ },
2445
+ at: {
2446
+ $ref: "#/$defs/scenario_position"
2447
+ }
2448
+ },
2449
+ required: ["with", "at"]
2450
+ },
2451
+ scenario_with_input_at_value: {
2452
+ type: "object",
2453
+ additionalProperties: false,
2454
+ properties: {
2455
+ id: {
2456
+ type: "string"
2457
+ },
2458
+ title: {
2459
+ type: "string"
2460
+ },
2461
+ subtitle: {
2462
+ type: "string"
2463
+ },
2464
+ with: {
2465
+ type: "string"
2466
+ },
2467
+ at: {
2468
+ type: "number"
2469
+ }
2470
+ },
2471
+ required: ["with", "at"]
2472
+ },
2473
+ scenario_input_at_position: {
2474
+ type: "object",
2475
+ additionalProperties: false,
2476
+ properties: {
2477
+ input: {
2478
+ type: "string"
2479
+ },
2480
+ at: {
2481
+ $ref: "#/$defs/scenario_position"
2482
+ }
2483
+ },
2484
+ required: ["input", "at"]
2485
+ },
2486
+ scenario_input_at_value: {
2487
+ type: "object",
2488
+ additionalProperties: false,
2489
+ properties: {
2490
+ input: {
2491
+ type: "string"
2492
+ },
2493
+ at: {
2494
+ type: "number"
2495
+ }
2496
+ },
2497
+ required: ["input", "at"]
2498
+ },
2499
+ scenario_input_setting: {
2500
+ oneOf: [{ $ref: "#/$defs/scenario_input_at_position" }, { $ref: "#/$defs/scenario_input_at_value" }]
2501
+ },
2502
+ scenario_input_setting_array: {
2503
+ type: "array",
2504
+ items: {
2505
+ $ref: "#/$defs/scenario_input_setting"
2506
+ },
2507
+ minItems: 1
2508
+ },
2509
+ scenario_with_multiple_input_settings: {
2510
+ type: "object",
2511
+ additionalProperties: false,
2512
+ properties: {
2513
+ id: {
2514
+ type: "string"
2515
+ },
2516
+ title: {
2517
+ type: "string"
2518
+ },
2519
+ subtitle: {
2520
+ type: "string"
2521
+ },
2522
+ with: {
2523
+ $ref: "#/$defs/scenario_input_setting_array"
2524
+ }
2525
+ },
2526
+ required: ["with"]
2527
+ },
2528
+ scenario_with_inputs_in_preset_at_position: {
2529
+ type: "object",
2530
+ additionalProperties: false,
2531
+ properties: {
2532
+ id: {
2533
+ type: "string"
2534
+ },
2535
+ title: {
2536
+ type: "string"
2537
+ },
2538
+ subtitle: {
2539
+ type: "string"
2540
+ },
2541
+ with_inputs: {
2542
+ type: "string",
2543
+ enum: ["all"]
2544
+ },
2545
+ at: {
2546
+ $ref: "#/$defs/scenario_position"
2547
+ }
2548
+ },
2549
+ required: ["with_inputs", "at"]
2550
+ },
2551
+ scenario_preset: {
2552
+ type: "object",
2553
+ additionalProperties: false,
2554
+ properties: {
2555
+ preset: {
2556
+ type: "string",
2557
+ enum: ["matrix"]
2558
+ }
2559
+ },
2560
+ required: ["preset"]
2561
+ },
2562
+ //
2563
+ // SCENARIO GROUPS
2564
+ //
2565
+ scenario_group_array_item: {
2566
+ type: "object",
2567
+ additionalProperties: false,
2568
+ properties: {
2569
+ scenario_group: {
2570
+ $ref: "#/$defs/scenario_group"
2571
+ }
2572
+ },
2573
+ required: ["scenario_group"]
2574
+ },
2575
+ scenario_group: {
2576
+ type: "object",
2577
+ additionalProperties: false,
2578
+ properties: {
2579
+ id: {
2580
+ type: "string"
2581
+ },
2582
+ title: {
2583
+ type: "string"
2584
+ },
2585
+ scenarios: {
2586
+ type: "array",
2587
+ items: {
2588
+ $ref: "#/$defs/scenario_group_scenarios_array_item"
2589
+ },
2590
+ minItems: 1
2591
+ }
2592
+ },
2593
+ required: ["title", "scenarios"]
2594
+ },
2595
+ scenario_group_scenarios_array_item: {
2596
+ oneOf: [{ $ref: "#/$defs/scenario_array_item" }, { $ref: "#/$defs/scenario_ref_array_item" }]
2597
+ },
2598
+ scenario_ref_id: {
2599
+ type: "string"
2600
+ },
2601
+ scenario_ref_object: {
2602
+ type: "object",
2603
+ additionalProperties: false,
2604
+ properties: {
2605
+ id: {
2606
+ type: "string"
2607
+ },
2608
+ title: {
2609
+ type: "string"
2610
+ },
2611
+ subtitle: {
2612
+ type: "string"
2613
+ }
2614
+ },
2615
+ required: ["id"]
2616
+ },
2617
+ scenario_ref: {
2618
+ oneOf: [{ $ref: "#/$defs/scenario_ref_id" }, { $ref: "#/$defs/scenario_ref_object" }]
2619
+ },
2620
+ scenario_ref_array_item: {
2621
+ type: "object",
2622
+ additionalProperties: false,
2623
+ properties: {
2624
+ scenario_ref: {
2625
+ $ref: "#/$defs/scenario_ref"
2626
+ }
2627
+ },
2628
+ required: ["scenario_ref"]
2629
+ },
2630
+ scenario_group_ref: {
2631
+ type: "object",
2632
+ additionalProperties: false,
2633
+ properties: {
2634
+ scenario_group_ref: {
2635
+ type: "string"
2636
+ }
2637
+ },
2638
+ required: ["scenario_group_ref"]
2639
+ },
2640
+ //
2641
+ // VIEWS
2642
+ //
2643
+ view: {
2644
+ type: "object",
2645
+ additionalProperties: false,
2646
+ properties: {
2647
+ title: {
2648
+ type: "string"
2649
+ },
2650
+ subtitle: {
2651
+ type: "string"
2652
+ },
2653
+ scenario_ref: {
2654
+ type: "string"
2655
+ },
2656
+ graphs: {
2657
+ $ref: "#/$defs/view_graphs"
2658
+ }
2659
+ },
2660
+ required: ["scenario_ref", "graphs"]
2661
+ },
2662
+ view_graphs: {
2663
+ oneOf: [{ $ref: "#/$defs/view_graphs_preset" }, { $ref: "#/$defs/view_graphs_array" }]
2664
+ },
2665
+ view_graphs_preset: {
2666
+ type: "string",
2667
+ enum: ["all"]
2668
+ },
2669
+ view_graphs_array: {
2670
+ type: "array",
2671
+ items: {
2672
+ type: "string"
2673
+ },
2674
+ minItems: 1
2675
+ },
2676
+ //
2677
+ // VIEW GROUPS
2678
+ //
2679
+ view_group_array_item: {
2680
+ type: "object",
2681
+ additionalProperties: false,
2682
+ properties: {
2683
+ view_group: {
2684
+ $ref: "#/$defs/view_group"
2685
+ }
2686
+ },
2687
+ required: ["view_group"]
2688
+ },
2689
+ view_group: {
2690
+ oneOf: [
2691
+ { $ref: "#/$defs/view_group_with_views_array" },
2692
+ { $ref: "#/$defs/view_group_shorthand_with_scenarios_array" }
2693
+ ]
2694
+ },
2695
+ view_group_with_views_array: {
2696
+ type: "object",
2697
+ additionalProperties: false,
2698
+ properties: {
2699
+ title: {
2700
+ type: "string"
2701
+ },
2702
+ views: {
2703
+ type: "array",
2704
+ items: {
2705
+ $ref: "#/$defs/view_group_views_array_item"
2706
+ },
2707
+ minItems: 1
2708
+ }
2709
+ },
2710
+ required: ["title", "views"]
2711
+ },
2712
+ view_group_views_array_item: {
2713
+ type: "object",
2714
+ additionalProperties: false,
2715
+ properties: {
2716
+ view: {
2717
+ $ref: "#/$defs/view"
2718
+ }
2719
+ }
2720
+ },
2721
+ view_group_shorthand_with_scenarios_array: {
2722
+ type: "object",
2723
+ additionalProperties: false,
2724
+ properties: {
2725
+ title: {
2726
+ type: "string"
2727
+ },
2728
+ scenarios: {
2729
+ type: "array",
2730
+ items: {
2731
+ $ref: "#/$defs/view_group_scenarios_array_item"
2732
+ },
2733
+ minItems: 1
2734
+ },
2735
+ graphs: {
2736
+ $ref: "#/$defs/view_graphs"
2737
+ }
2738
+ },
2739
+ required: ["title", "scenarios", "graphs"]
2740
+ },
2741
+ view_group_scenarios_array_item: {
2742
+ oneOf: [{ $ref: "#/$defs/scenario_ref_array_item" }, { $ref: "#/$defs/scenario_group_ref" }]
2743
+ }
2744
+ }
2745
+ };
2746
+
2747
+ // src/comparison/config/parse/comparison-parser.ts
2748
+ function parseComparisonSpecs(specSource) {
2749
+ const scenarios = [];
2750
+ const scenarioGroups = [];
2751
+ const viewGroups = [];
2752
+ const ajv = new Ajv2();
2753
+ const validate = ajv.compile(comparison_schema_default);
2754
+ let parsed;
2755
+ switch (specSource.kind) {
2756
+ case "json":
2757
+ parsed = JSON.parse(specSource.content);
2758
+ break;
2759
+ case "yaml":
2760
+ parsed = yaml2.parse(specSource.content);
2761
+ break;
2762
+ default:
2763
+ assertNever9(specSource.kind);
2764
+ }
2765
+ if (validate(parsed)) {
2766
+ for (const specItem of parsed) {
2767
+ if ("scenario" in specItem) {
2768
+ scenarios.push(scenarioSpecFromParsed(specItem.scenario));
2769
+ } else if ("scenario_group" in specItem) {
2770
+ scenarioGroups.push(scenarioGroupSpecFromParsed(specItem.scenario_group));
2771
+ } else if ("view_group" in specItem) {
2772
+ viewGroups.push(viewGroupSpecFromParsed(specItem.view_group));
2773
+ }
2774
+ }
2775
+ } else {
2776
+ let msg = "Failed to parse YAML comparison definitions";
2777
+ for (const error of validate.errors || []) {
2778
+ if (error.message) {
2779
+ msg += `
2780
+ ${error.message}`;
2781
+ }
2782
+ }
2783
+ return err2(new Error(msg));
2784
+ }
2785
+ return ok2({
2786
+ scenarios,
2787
+ scenarioGroups,
2788
+ viewGroups
2789
+ });
2790
+ }
2791
+ function scenarioSpecFromParsed(parsedScenario) {
2792
+ if (parsedScenario.preset === "matrix") {
2793
+ return {
2794
+ kind: "scenario-matrix"
2795
+ };
2796
+ }
2797
+ if (parsedScenario.with !== void 0) {
2798
+ let inputSpecs;
2799
+ if (Array.isArray(parsedScenario.with)) {
2800
+ const parsedInputs = parsedScenario.with;
2801
+ inputSpecs = parsedInputs.map(inputSpecFromParsed);
2802
+ } else {
2803
+ inputSpecs = [
2804
+ inputSpecFromParsed({
2805
+ input: parsedScenario.with,
2806
+ at: parsedScenario.at
2807
+ })
2808
+ ];
2809
+ }
2810
+ return {
2811
+ kind: "scenario-with-inputs",
2812
+ id: parsedScenario.id,
2813
+ title: parsedScenario.title,
2814
+ subtitle: parsedScenario.subtitle,
2815
+ inputs: inputSpecs
2816
+ };
2817
+ }
2818
+ if (parsedScenario.with_inputs === "all") {
2819
+ return {
2820
+ kind: "scenario-with-all-inputs",
2821
+ id: parsedScenario.id,
2822
+ title: parsedScenario.title,
2823
+ subtitle: parsedScenario.subtitle,
2824
+ position: parsedScenario.at
2825
+ };
2826
+ }
2827
+ throw new Error(`Unable to convert parsed scenario: ${JSON.stringify(parsedScenario)}`);
2828
+ }
2829
+ function inputSpecFromParsed(parsedInput) {
2830
+ if (typeof parsedInput.at === "number") {
2831
+ const value = parsedInput.at;
2832
+ return {
2833
+ kind: "input-at-value",
2834
+ inputName: parsedInput.input,
2835
+ value
2836
+ };
2837
+ } else {
2838
+ return {
2839
+ kind: "input-at-position",
2840
+ inputName: parsedInput.input,
2841
+ position: parsedInput.at
2842
+ };
2843
+ }
2844
+ }
2845
+ function scenarioGroupSpecFromParsed(parsedScenarioGroup) {
2846
+ const scenarioSpecs = parsedScenarioGroup.scenarios.map(
2847
+ (parsedScenarioOrRef) => {
2848
+ if ("scenario_ref" in parsedScenarioOrRef) {
2849
+ return scenarioRefSpecFromParsed(parsedScenarioOrRef);
2850
+ } else {
2851
+ return scenarioSpecFromParsed(parsedScenarioOrRef.scenario);
2852
+ }
2853
+ }
2854
+ );
2855
+ return {
2856
+ kind: "scenario-group",
2857
+ id: parsedScenarioGroup.id,
2858
+ title: parsedScenarioGroup.title,
2859
+ scenarios: scenarioSpecs
2860
+ };
2861
+ }
2862
+ function scenarioRefSpecFromParsed(parsedScenarioRef) {
2863
+ if (typeof parsedScenarioRef.scenario_ref === "string") {
2864
+ return {
2865
+ kind: "scenario-ref",
2866
+ scenarioId: parsedScenarioRef.scenario_ref
2867
+ };
2868
+ } else {
2869
+ return {
2870
+ kind: "scenario-ref",
2871
+ scenarioId: parsedScenarioRef.scenario_ref.id,
2872
+ title: parsedScenarioRef.scenario_ref.title,
2873
+ subtitle: parsedScenarioRef.scenario_ref.subtitle
2874
+ };
2875
+ }
2876
+ }
2877
+ function scenarioGroupRefSpecFromParsed(parsedGroupRef) {
2878
+ return {
2879
+ kind: "scenario-group-ref",
2880
+ groupId: parsedGroupRef.scenario_group_ref
2881
+ };
2882
+ }
2883
+ function viewSpecFromParsed(parsedView) {
2884
+ return {
2885
+ kind: "view",
2886
+ title: parsedView.title,
2887
+ subtitle: parsedView.subtitle,
2888
+ scenarioId: parsedView.scenario_ref,
2889
+ graphs: viewGraphsSpecFromParsed(parsedView.graphs)
2890
+ };
2891
+ }
2892
+ function viewGraphsSpecFromParsed(parsedGraphs) {
2893
+ if (parsedGraphs === "all") {
2894
+ return {
2895
+ kind: "graphs-preset",
2896
+ preset: "all"
2897
+ };
2898
+ } else {
2899
+ return {
2900
+ kind: "graphs-array",
2901
+ graphIds: parsedGraphs
2902
+ };
2903
+ }
2904
+ }
2905
+ function viewGroupSpecFromParsed(parsedViewGroup) {
2906
+ if (parsedViewGroup.views !== void 0) {
2907
+ return {
2908
+ kind: "view-group-with-views",
2909
+ title: parsedViewGroup.title,
2910
+ views: parsedViewGroup.views.map((item) => viewSpecFromParsed(item.view))
2911
+ };
2912
+ } else if (parsedViewGroup.scenarios !== void 0) {
2913
+ const scenarios = parsedViewGroup.scenarios.map(
2914
+ (parsedScenarioOrGroupRef) => {
2915
+ if ("scenario_ref" in parsedScenarioOrGroupRef) {
2916
+ return scenarioRefSpecFromParsed(parsedScenarioOrGroupRef);
2917
+ } else if ("scenario_group_ref" in parsedScenarioOrGroupRef) {
2918
+ return scenarioGroupRefSpecFromParsed(parsedScenarioOrGroupRef);
2919
+ } else {
2920
+ throw new Error("Invalid view group");
2921
+ }
2922
+ }
2923
+ );
2924
+ return {
2925
+ kind: "view-group-with-scenarios",
2926
+ title: parsedViewGroup.title,
2927
+ scenarios,
2928
+ graphs: viewGraphsSpecFromParsed(parsedViewGroup.graphs)
2929
+ };
2930
+ } else {
2931
+ throw new Error("Invalid view group");
2932
+ }
2933
+ }
2934
+
2935
+ // src/comparison/config/resolve/comparison-resolver.ts
2936
+ import { assertNever as assertNever11 } from "assert-never";
2937
+
2938
+ // src/comparison/config/resolve/comparison-scenario-specs.ts
2939
+ import { assertNever as assertNever10 } from "assert-never";
2940
+ function scenarioSpecsFromSettings(settings) {
2941
+ switch (settings.kind) {
2942
+ case "all-inputs-settings": {
2943
+ const scenario = allInputsAtPositionSpec(settings.position);
2944
+ return [scenario, scenario];
2945
+ }
2946
+ case "input-settings": {
2947
+ const specL = scenarioSpecFromInputs(settings.inputs, "left");
2948
+ const specR = scenarioSpecFromInputs(settings.inputs, "right");
2949
+ return [specL, specR];
2950
+ }
2951
+ default:
2952
+ assertNever10(settings);
2953
+ }
2954
+ }
2955
+ function scenarioSpecFromInputs(inputs, side) {
2956
+ const settings = [];
2957
+ for (const input of inputs) {
2958
+ const state = side === "left" ? input.stateL : input.stateR;
2959
+ if (state.inputVar === void 0) {
2960
+ return void 0;
2961
+ }
2962
+ settings.push(inputSettingFromResolvedInputState(state));
2963
+ }
2964
+ return inputSettingsSpec(settings);
2965
+ }
2966
+ function inputSettingFromResolvedInputState(state) {
2967
+ const varId = state.inputVar.varId;
2968
+ if (state.position) {
2969
+ return positionSetting(varId, state.position);
2970
+ } else {
2971
+ return valueSetting(varId, state.value);
2972
+ }
2973
+ }
2974
+
2975
+ // src/comparison/config/resolve/comparison-resolver.ts
2976
+ function resolveComparisonSpecs(modelInputsL, modelInputsR, specs) {
2977
+ let key = 1;
2978
+ const genKey = () => {
2979
+ return `${key++}`;
2980
+ };
2981
+ const resolvedScenarios = new ResolvedScenarios();
2982
+ for (const scenarioSpec of specs.scenarios) {
2983
+ resolvedScenarios.add(resolveScenariosFromSpec(modelInputsL, modelInputsR, scenarioSpec, genKey));
2984
+ }
2985
+ const partiallyResolvedScenarioGroups = [];
2986
+ for (const scenarioGroupSpec of specs.scenarioGroups) {
2987
+ const scenariosForGroup = [];
2988
+ for (const scenarioItem of scenarioGroupSpec.scenarios) {
2989
+ if (scenarioItem.kind === "scenario-ref") {
2990
+ scenariosForGroup.push(scenarioItem);
2991
+ } else {
2992
+ const scenarios = resolveScenariosFromSpec(modelInputsL, modelInputsR, scenarioItem, genKey);
2993
+ resolvedScenarios.add(scenarios);
2994
+ scenariosForGroup.push(...scenarios);
2995
+ }
2996
+ }
2997
+ partiallyResolvedScenarioGroups.push({
2998
+ spec: scenarioGroupSpec,
2999
+ scenarios: scenariosForGroup
3000
+ });
3001
+ }
3002
+ const resolvedScenarioGroups = new ResolvedScenarioGroups();
3003
+ for (const partiallyResolvedGroup of partiallyResolvedScenarioGroups) {
3004
+ const scenariosForGroup = [];
3005
+ for (const scenarioItem of partiallyResolvedGroup.scenarios) {
3006
+ if (scenarioItem.kind === "scenario-ref") {
3007
+ const referencedScenario = resolvedScenarios.getScenarioForId(scenarioItem.scenarioId);
3008
+ if (referencedScenario) {
3009
+ const resolvedScenario = __spreadValues({}, referencedScenario);
3010
+ if (scenarioItem.title) {
3011
+ resolvedScenario.title = scenarioItem.title;
3012
+ }
3013
+ if (scenarioItem.subtitle) {
3014
+ resolvedScenario.subtitle = scenarioItem.subtitle;
3015
+ }
3016
+ scenariosForGroup.push(resolvedScenario);
3017
+ } else {
3018
+ scenariosForGroup.push({
3019
+ kind: "unresolved-scenario-ref",
3020
+ scenarioId: scenarioItem.scenarioId
3021
+ });
3022
+ }
3023
+ } else {
3024
+ scenariosForGroup.push(scenarioItem);
3025
+ }
3026
+ }
3027
+ resolvedScenarioGroups.add({
3028
+ kind: "scenario-group",
3029
+ id: partiallyResolvedGroup.spec.id,
3030
+ title: partiallyResolvedGroup.spec.title,
3031
+ scenarios: scenariosForGroup
3032
+ });
3033
+ }
3034
+ const resolvedViewGroups = [];
3035
+ for (const viewGroupSpec of specs.viewGroups) {
3036
+ const resolvedViewGroup = resolveViewGroupFromSpec(resolvedScenarios, resolvedScenarioGroups, viewGroupSpec);
3037
+ resolvedViewGroups.push(resolvedViewGroup);
3038
+ }
3039
+ return {
3040
+ scenarios: resolvedScenarios.getAll(),
3041
+ scenarioGroups: resolvedScenarioGroups.getAll(),
3042
+ viewGroups: resolvedViewGroups
3043
+ };
3044
+ }
3045
+ var ResolvedScenarios = class {
3046
+ constructor() {
3047
+ /** The array of all resolved scenarios. */
3048
+ this.resolvedScenarios = [];
3049
+ /** The set of resolved scenarios that include an ID, keyed by ID. */
3050
+ this.resolvedScenariosById = /* @__PURE__ */ new Map();
3051
+ }
3052
+ add(scenarios) {
3053
+ for (const resolvedScenario of scenarios) {
3054
+ this.resolvedScenarios.push(resolvedScenario);
3055
+ if (resolvedScenario.id !== void 0) {
3056
+ if (this.resolvedScenariosById.has(resolvedScenario.id)) {
3057
+ throw new Error(`Multiple scenarios defined with the same id (${resolvedScenario.id})`);
3058
+ }
3059
+ this.resolvedScenariosById.set(resolvedScenario.id, resolvedScenario);
3060
+ }
3061
+ }
3062
+ }
3063
+ getAll() {
3064
+ return this.resolvedScenarios;
3065
+ }
3066
+ getScenarioForId(id) {
3067
+ return this.resolvedScenariosById.get(id);
3068
+ }
3069
+ };
3070
+ function resolveScenariosFromSpec(modelInputsL, modelInputsR, scenarioSpec, genKey) {
3071
+ switch (scenarioSpec.kind) {
3072
+ case "scenario-matrix":
3073
+ return resolveScenarioMatrix(modelInputsL, modelInputsR, genKey);
3074
+ case "scenario-with-all-inputs": {
3075
+ const position = inputPosition2(scenarioSpec.position);
3076
+ return [
3077
+ resolveScenarioWithAllInputsAtPosition(
3078
+ genKey(),
3079
+ scenarioSpec.id,
3080
+ scenarioSpec.title,
3081
+ scenarioSpec.subtitle,
3082
+ position
3083
+ )
3084
+ ];
3085
+ }
3086
+ case "scenario-with-inputs": {
3087
+ return [
3088
+ resolveScenarioForInputSpecs(
3089
+ modelInputsL,
3090
+ modelInputsR,
3091
+ genKey(),
3092
+ scenarioSpec.id,
3093
+ scenarioSpec.title,
3094
+ scenarioSpec.subtitle,
3095
+ scenarioSpec.inputs
3096
+ )
3097
+ ];
3098
+ }
3099
+ case "scenario-with-distinct-inputs": {
3100
+ return [
3101
+ resolveScenarioForDistinctInputSpecs(
3102
+ modelInputsL,
3103
+ modelInputsR,
3104
+ genKey(),
3105
+ scenarioSpec.id,
3106
+ scenarioSpec.title,
3107
+ scenarioSpec.subtitle,
3108
+ scenarioSpec.inputsL,
3109
+ scenarioSpec.inputsR
3110
+ )
3111
+ ];
3112
+ }
3113
+ default:
3114
+ assertNever11(scenarioSpec);
3115
+ }
3116
+ }
3117
+ function resolveScenarioMatrix(modelInputsL, modelInputsR, genKey) {
3118
+ const resolvedScenarios = [];
3119
+ resolvedScenarios.push(
3120
+ resolveScenarioWithAllInputsAtPosition(genKey(), void 0, void 0, void 0, "at-default")
3121
+ );
3122
+ const inputIdAliases = /* @__PURE__ */ new Set();
3123
+ modelInputsL.getAllInputIdAliases().forEach((alias) => inputIdAliases.add(alias));
3124
+ modelInputsR.getAllInputIdAliases().forEach((alias) => inputIdAliases.add(alias));
3125
+ for (const inputIdAlias of inputIdAliases) {
3126
+ const inputAtMin = {
3127
+ kind: "input-at-position",
3128
+ inputName: inputIdAlias,
3129
+ position: "min"
3130
+ };
3131
+ const inputAtMax = {
3132
+ kind: "input-at-position",
3133
+ inputName: inputIdAlias,
3134
+ position: "max"
3135
+ };
3136
+ resolvedScenarios.push(
3137
+ resolveScenarioForInputSpecs(modelInputsL, modelInputsR, genKey(), void 0, void 0, void 0, [inputAtMin])
3138
+ );
3139
+ resolvedScenarios.push(
3140
+ resolveScenarioForInputSpecs(modelInputsL, modelInputsR, genKey(), void 0, void 0, void 0, [inputAtMax])
3141
+ );
3142
+ }
3143
+ return resolvedScenarios;
3144
+ }
3145
+ function resolveScenarioWithAllInputsAtPosition(key, id, title, subtitle, position) {
3146
+ const settings = {
3147
+ kind: "all-inputs-settings",
3148
+ position
3149
+ };
3150
+ const [specL, specR] = scenarioSpecsFromSettings(settings);
3151
+ return {
3152
+ kind: "scenario",
3153
+ key,
3154
+ id,
3155
+ title,
3156
+ subtitle,
3157
+ settings,
3158
+ specL,
3159
+ specR
3160
+ };
3161
+ }
3162
+ function resolveScenarioForInputSpecs(modelInputsL, modelInputsR, key, id, title, subtitle, inputSpecs) {
3163
+ const resolvedInputs = inputSpecs.map((inputSpec) => {
3164
+ switch (inputSpec.kind) {
3165
+ case "input-at-position":
3166
+ return resolveInputForName(modelInputsL, modelInputsR, inputSpec.inputName, inputSpec.position);
3167
+ case "input-at-value":
3168
+ return resolveInputForName(modelInputsL, modelInputsR, inputSpec.inputName, inputSpec.value);
3169
+ default:
3170
+ assertNever11(inputSpec);
3171
+ }
3172
+ });
3173
+ const settings = {
3174
+ kind: "input-settings",
3175
+ inputs: resolvedInputs
3176
+ };
3177
+ const [specL, specR] = scenarioSpecsFromSettings(settings);
3178
+ return {
3179
+ kind: "scenario",
3180
+ key,
3181
+ id,
3182
+ title,
3183
+ subtitle,
3184
+ settings,
3185
+ specL,
3186
+ specR
3187
+ };
3188
+ }
3189
+ function resolveScenarioForDistinctInputSpecs(modelInputsL, modelInputsR, key, id, title, subtitle, inputSpecsL, inputSpecsR) {
3190
+ const inputsWithErrors = [];
3191
+ const settingsL = [];
3192
+ const settingsR = [];
3193
+ function resolveInputSpec(side, modelInputs, inputSpec) {
3194
+ let inputState;
3195
+ switch (inputSpec.kind) {
3196
+ case "input-at-position":
3197
+ inputState = resolveInputForNameInModel(modelInputs, inputSpec.inputName, inputSpec.position);
3198
+ break;
3199
+ case "input-at-value":
3200
+ inputState = resolveInputForNameInModel(modelInputs, inputSpec.inputName, inputSpec.value);
3201
+ break;
3202
+ default:
3203
+ assertNever11(inputSpec);
3204
+ }
3205
+ if (inputState.error !== void 0) {
3206
+ inputsWithErrors.push({
3207
+ requestedName: inputSpec.inputName,
3208
+ stateL: side === "left" ? inputState : {},
3209
+ stateR: side === "right" ? inputState : {}
3210
+ });
3211
+ } else {
3212
+ const inputSetting = inputSettingFromResolvedInputState(inputState);
3213
+ if (side === "left") {
3214
+ settingsL.push(inputSetting);
3215
+ } else {
3216
+ settingsR.push(inputSetting);
3217
+ }
3218
+ }
3219
+ }
3220
+ inputSpecsL.forEach((inputSpec) => resolveInputSpec("left", modelInputsL, inputSpec));
3221
+ inputSpecsR.forEach((inputSpec) => resolveInputSpec("right", modelInputsR, inputSpec));
3222
+ let specL;
3223
+ let specR;
3224
+ if (inputsWithErrors.length === 0) {
3225
+ specL = inputSettingsSpec(settingsL);
3226
+ specR = inputSettingsSpec(settingsR);
3227
+ }
3228
+ return {
3229
+ kind: "scenario",
3230
+ key,
3231
+ id,
3232
+ title,
3233
+ subtitle,
3234
+ settings: {
3235
+ kind: "input-settings",
3236
+ inputs: inputsWithErrors
3237
+ },
3238
+ specL,
3239
+ specR
3240
+ };
3241
+ }
3242
+ function resolveInputForName(modelInputsL, modelInputsR, inputName, at) {
3243
+ return {
3244
+ requestedName: inputName,
3245
+ stateL: resolveInputForNameInModel(modelInputsL, inputName, at),
3246
+ stateR: resolveInputForNameInModel(modelInputsR, inputName, at)
3247
+ };
3248
+ }
3249
+ function resolveInputForNameInModel(modelInputs, inputName, at) {
3250
+ const inputVar = modelInputs.getInputVarForName(inputName);
3251
+ if (inputVar) {
3252
+ return resolveInputVar(inputVar, at);
3253
+ } else {
3254
+ return {
3255
+ error: {
3256
+ kind: "unknown-input"
3257
+ }
3258
+ };
3259
+ }
3260
+ }
3261
+ function resolveInputVar(inputVar, at) {
3262
+ if (typeof at === "number") {
3263
+ const value = at;
3264
+ return resolveInputVarAtValue(inputVar, value);
3265
+ } else {
3266
+ const position = inputPosition2(at);
3267
+ return resolveInputVarAtPosition(inputVar, position);
3268
+ }
3269
+ }
3270
+ function resolveInputVarAtPosition(inputVar, position) {
3271
+ return {
3272
+ inputVar,
3273
+ position,
3274
+ value: inputValueAtPosition2(inputVar, position)
3275
+ };
3276
+ }
3277
+ function resolveInputVarAtValue(inputVar, value) {
3278
+ if (value >= inputVar.minValue && value <= inputVar.maxValue) {
3279
+ return {
3280
+ inputVar,
3281
+ value
3282
+ };
3283
+ } else {
3284
+ return {
3285
+ error: {
3286
+ kind: "invalid-value"
3287
+ }
3288
+ };
3289
+ }
3290
+ }
3291
+ function inputPosition2(position) {
3292
+ switch (position) {
3293
+ case "default":
3294
+ return "at-default";
3295
+ case "min":
3296
+ return "at-minimum";
3297
+ case "max":
3298
+ return "at-maximum";
3299
+ default:
3300
+ return void 0;
3301
+ }
3302
+ }
3303
+ function inputValueAtPosition2(inputVar, position) {
3304
+ switch (position) {
3305
+ case "at-default":
3306
+ return inputVar.defaultValue;
3307
+ case "at-minimum":
3308
+ return inputVar.minValue;
3309
+ case "at-maximum":
3310
+ return inputVar.maxValue;
3311
+ default:
3312
+ assertNever11(position);
3313
+ }
3314
+ }
3315
+ var ResolvedScenarioGroups = class {
3316
+ constructor() {
3317
+ /** The array of all resolved scenario groups. */
3318
+ this.resolvedGroups = [];
3319
+ /** The set of resolved scenario groups that include an ID, keyed by ID. */
3320
+ this.resolvedGroupsById = /* @__PURE__ */ new Map();
3321
+ }
3322
+ add(group) {
3323
+ this.resolvedGroups.push(group);
3324
+ if (group.id !== void 0) {
3325
+ if (this.resolvedGroupsById.has(group.id)) {
3326
+ throw new Error(`Multiple scenario groups defined with the same id (${group.id})`);
3327
+ }
3328
+ this.resolvedGroupsById.set(group.id, group);
3329
+ }
3330
+ }
3331
+ getAll() {
3332
+ return this.resolvedGroups;
3333
+ }
3334
+ getGroupForId(id) {
3335
+ return this.resolvedGroupsById.get(id);
3336
+ }
3337
+ };
3338
+ function resolveGraphsFromSpec(graphsSpec) {
3339
+ switch (graphsSpec.kind) {
3340
+ case "graphs-preset":
3341
+ return "all";
3342
+ case "graphs-array":
3343
+ return graphsSpec.graphIds;
3344
+ default:
3345
+ assertNever11(graphsSpec);
3346
+ }
3347
+ }
3348
+ function resolveViewForScenarioId(resolvedScenarios, viewTitle, viewSubtitle, scenarioId, graphs) {
3349
+ const resolvedScenario = resolvedScenarios.getScenarioForId(scenarioId);
3350
+ if (resolvedScenario) {
3351
+ return resolveViewForScenario(viewTitle, viewSubtitle, resolvedScenario, graphs);
3352
+ } else {
3353
+ return unresolvedViewForScenarioId(viewTitle, viewSubtitle, scenarioId);
3354
+ }
3355
+ }
3356
+ function resolveViewForScenarioRefSpec(resolvedScenarios, refSpec, graphs) {
3357
+ const resolvedScenario = resolvedScenarios.getScenarioForId(refSpec.scenarioId);
3358
+ if (resolvedScenario) {
3359
+ return resolveViewForScenario(refSpec.title, refSpec.subtitle, resolvedScenario, graphs);
3360
+ } else {
3361
+ return unresolvedViewForScenarioId(void 0, void 0, refSpec.scenarioId);
3362
+ }
3363
+ }
3364
+ function resolveViewForScenario(viewTitle, viewSubtitle, resolvedScenario, graphs) {
3365
+ if (viewTitle === void 0) {
3366
+ viewTitle = resolvedScenario.title;
3367
+ if (viewTitle === void 0) {
3368
+ viewTitle = "Untitled view";
3369
+ }
3370
+ if (viewSubtitle === void 0) {
3371
+ viewSubtitle = resolvedScenario.subtitle;
3372
+ }
3373
+ }
3374
+ return {
3375
+ kind: "view",
3376
+ title: viewTitle,
3377
+ subtitle: viewSubtitle,
3378
+ scenario: resolvedScenario,
3379
+ graphs
3380
+ };
3381
+ }
3382
+ function unresolvedViewForScenarioId(viewTitle, viewSubtitle, scenarioId) {
3383
+ return {
3384
+ kind: "unresolved-view",
3385
+ title: viewTitle,
3386
+ subtitle: viewSubtitle,
3387
+ scenarioId
3388
+ };
3389
+ }
3390
+ function unresolvedViewForScenarioGroupId(viewTitle, viewSubtitle, scenarioGroupId) {
3391
+ return {
3392
+ kind: "unresolved-view",
3393
+ title: viewTitle,
3394
+ subtitle: viewSubtitle,
3395
+ scenarioGroupId
3396
+ };
3397
+ }
3398
+ function resolveViewGroupFromSpec(resolvedScenarios, resolvedScenarioGroups, viewGroupSpec) {
3399
+ let views;
3400
+ switch (viewGroupSpec.kind) {
3401
+ case "view-group-with-views": {
3402
+ 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
+ );
3411
+ });
3412
+ break;
3413
+ }
3414
+ case "view-group-with-scenarios": {
3415
+ const graphs = resolveGraphsFromSpec(viewGroupSpec.graphs);
3416
+ views = [];
3417
+ for (const refSpec of viewGroupSpec.scenarios) {
3418
+ switch (refSpec.kind) {
3419
+ case "scenario-ref":
3420
+ views.push(resolveViewForScenarioRefSpec(resolvedScenarios, refSpec, graphs));
3421
+ break;
3422
+ case "scenario-group-ref": {
3423
+ const resolvedGroup = resolvedScenarioGroups.getGroupForId(refSpec.groupId);
3424
+ if (resolvedGroup) {
3425
+ for (const scenario of resolvedGroup.scenarios) {
3426
+ switch (scenario.kind) {
3427
+ case "unresolved-scenario-ref":
3428
+ views.push(unresolvedViewForScenarioId(void 0, void 0, scenario.scenarioId));
3429
+ break;
3430
+ case "scenario":
3431
+ views.push(resolveViewForScenario(void 0, void 0, scenario, graphs));
3432
+ break;
3433
+ default:
3434
+ assertNever11(scenario);
3435
+ }
3436
+ }
3437
+ } else {
3438
+ views.push(unresolvedViewForScenarioGroupId(void 0, void 0, refSpec.groupId));
3439
+ }
3440
+ break;
3441
+ }
3442
+ default:
3443
+ assertNever11(refSpec);
3444
+ }
3445
+ }
3446
+ break;
3447
+ }
3448
+ default:
3449
+ assertNever11(viewGroupSpec);
3450
+ }
3451
+ return {
3452
+ kind: "view-group",
3453
+ title: viewGroupSpec.title,
3454
+ views
3455
+ };
3456
+ }
3457
+
3458
+ // src/comparison/config/comparison-config.ts
3459
+ function resolveComparisonSpecsFromSources(modelInputsL, modelInputsR, specSources) {
3460
+ const combinedSpecs = {
3461
+ scenarios: [],
3462
+ scenarioGroups: [],
3463
+ viewGroups: []
3464
+ };
3465
+ for (const specSource of specSources) {
3466
+ let specs;
3467
+ if ("kind" in specSource) {
3468
+ const parseResult = parseComparisonSpecs(specSource);
3469
+ if (parseResult.isOk()) {
3470
+ specs = parseResult.value;
3471
+ } else {
3472
+ const filenamePart = specSource.filename ? ` in ${specSource.filename}` : "";
3473
+ console.error(`ERROR: Failed to parse comparison spec${filenamePart}, skipping`);
3474
+ continue;
3475
+ }
3476
+ } else {
3477
+ specs = specSource;
3478
+ }
3479
+ combinedSpecs.scenarios.push(...specs.scenarios);
3480
+ combinedSpecs.scenarioGroups.push(...specs.scenarioGroups);
3481
+ combinedSpecs.viewGroups.push(...specs.viewGroups);
3482
+ }
3483
+ return resolveComparisonSpecs(modelInputsL, modelInputsR, combinedSpecs);
3484
+ }
3485
+
3486
+ // src/comparison/config/comparison-datasets.ts
3487
+ function getComparisonDatasets(modelSpecL, modelSpecR, datasetOptions) {
3488
+ return new ComparisonDatasetsImpl(modelSpecL, modelSpecR, datasetOptions);
3489
+ }
3490
+ var ComparisonDatasetsImpl = class {
3491
+ /**
3492
+ * @param modelSpecL The model spec for the "left" bundle being compared.
3493
+ * @param modelSpecR The model spec for the "right" bundle being compared.
3494
+ * @param datasetOptions The custom configuration for the datasets to be compared.
3495
+ */
3496
+ constructor(modelSpecL, modelSpecR, datasetOptions) {
3497
+ this.datasetOptions = datasetOptions;
3498
+ const renamedDatasetKeys = datasetOptions == null ? void 0 : datasetOptions.renamedDatasetKeys;
3499
+ const invertedRenamedKeys = /* @__PURE__ */ new Map();
3500
+ renamedDatasetKeys == null ? void 0 : renamedDatasetKeys.forEach((newKey, oldKey) => {
3501
+ invertedRenamedKeys.set(newKey, oldKey);
3502
+ });
3503
+ function leftKeyForRightKey(rightKey) {
3504
+ return invertedRenamedKeys.get(rightKey) || rightKey;
3505
+ }
3506
+ const allOutputVarKeysSet = /* @__PURE__ */ new Set();
3507
+ const modelOutputVarKeysSet = /* @__PURE__ */ new Set();
3508
+ function addOutputVars(outputVars, handleRenames) {
3509
+ outputVars.forEach((outputVar, key) => {
3510
+ const remappedKey = handleRenames ? leftKeyForRightKey(key) : key;
3511
+ allOutputVarKeysSet.add(remappedKey);
2202
3512
  if (outputVar.sourceName === void 0) {
2203
3513
  modelOutputVarKeysSet.add(remappedKey);
2204
3514
  }
2205
3515
  });
2206
3516
  }
2207
- addOutputVars(bundleL.modelSpec.outputVars, false);
2208
- addOutputVars(bundleR.modelSpec.outputVars, true);
3517
+ addOutputVars(modelSpecL.outputVars, false);
3518
+ addOutputVars(modelSpecR.outputVars, true);
2209
3519
  this.allOutputVarKeys = Array.from(allOutputVarKeysSet);
2210
3520
  this.modelOutputVarKeys = Array.from(modelOutputVarKeysSet);
2211
- }
2212
- getDatasetKeysForScenario(scenario) {
2213
- if (scenario.kind === "all-inputs" && scenario.position === "at-default") {
2214
- return this.allOutputVarKeys;
2215
- } else {
2216
- return this.modelOutputVarKeys;
3521
+ this.allDatasets = /* @__PURE__ */ new Map();
3522
+ for (const datasetKeyL of this.allOutputVarKeys) {
3523
+ const datasetKeyR = (renamedDatasetKeys == null ? void 0 : renamedDatasetKeys.get(datasetKeyL)) || datasetKeyL;
3524
+ const outputVarL = modelSpecL.outputVars.get(datasetKeyL);
3525
+ const outputVarR = modelSpecR.outputVars.get(datasetKeyR);
3526
+ this.allDatasets.set(datasetKeyL, {
3527
+ kind: "dataset",
3528
+ key: datasetKeyL,
3529
+ outputVarL,
3530
+ outputVarR
3531
+ });
2217
3532
  }
2218
3533
  }
2219
- getDatasetInfo(datasetKey) {
3534
+ // from ComparisonDatasets interface
3535
+ getAllDatasets() {
3536
+ return this.allDatasets.values();
3537
+ }
3538
+ // from ComparisonDatasets interface
3539
+ getDataset(datasetKey) {
3540
+ return this.allDatasets.get(datasetKey);
3541
+ }
3542
+ // from ComparisonDatasets interface
3543
+ getDatasetKeysForScenario(scenario) {
2220
3544
  var _a;
2221
- const modelSpecL = this.bundleL.modelSpec;
2222
- const modelSpecR = this.bundleR.modelSpec;
2223
- const datasetKeyL = datasetKey;
2224
- const datasetKeyR = ((_a = this.renamedDatasetKeys) == null ? void 0 : _a.get(datasetKeyL)) || datasetKeyL;
2225
- const outputVarL = modelSpecL.outputVars.get(datasetKeyL);
2226
- const outputVarR = modelSpecR.outputVars.get(datasetKeyR);
2227
- let varName;
2228
- let newVarName;
2229
- let sourceName;
2230
- let newSourceName;
2231
- if (outputVarL && outputVarR && outputVarL.varName != outputVarR.varName) {
2232
- varName = outputVarL.varName;
2233
- newVarName = outputVarR.varName;
2234
- } else {
2235
- const outputVar = outputVarR || outputVarL;
2236
- varName = (outputVar == null ? void 0 : outputVar.varName) || "Unknown";
2237
- }
2238
- if (outputVarL && outputVarR && outputVarL.sourceName != outputVarR.sourceName) {
2239
- sourceName = outputVarL.sourceName;
2240
- newSourceName = outputVarR.sourceName;
3545
+ if (((_a = this.datasetOptions) == null ? void 0 : _a.datasetKeysForScenario) !== void 0) {
3546
+ return this.datasetOptions.datasetKeysForScenario(this.allOutputVarKeys, scenario);
2241
3547
  } else {
2242
- const outputVar = outputVarR || outputVarL;
2243
- sourceName = outputVar == null ? void 0 : outputVar.sourceName;
3548
+ if (scenario.settings.kind === "all-inputs-settings" && scenario.settings.position === "at-default") {
3549
+ return this.allOutputVarKeys;
3550
+ } else {
3551
+ return this.modelOutputVarKeys;
3552
+ }
2244
3553
  }
2245
- return {
2246
- varName,
2247
- newVarName,
2248
- sourceName,
2249
- newSourceName,
2250
- relatedItems: (outputVarR == null ? void 0 : outputVarR.relatedItems) || []
2251
- };
2252
3554
  }
2253
3555
  };
2254
3556
 
2255
- // src/config/scenario-manager.ts
2256
- import { assertNever as assertNever8 } from "assert-never";
2257
- var ScenarioManager = class {
2258
- constructor(bundleL, bundleR) {
2259
- this.bundleL = bundleL;
2260
- this.bundleR = bundleR;
2261
- this.scenarios = /* @__PURE__ */ new Map();
2262
- this.scenarioInfo = /* @__PURE__ */ new Map();
2263
- this.defaultInfoForGroup = /* @__PURE__ */ new Map();
2264
- this.groupInfo = /* @__PURE__ */ new Map();
2265
- }
2266
- getScenarios() {
2267
- return [...this.scenarios.values()];
2268
- }
2269
- getScenario(scenarioKey) {
2270
- return this.scenarios.get(scenarioKey);
2271
- }
2272
- getScenarioGroupInfo(groupKey) {
2273
- return this.groupInfo.get(groupKey);
2274
- }
2275
- getScenarioInfo(scenario, groupKey) {
2276
- if (scenario.key === "all_inputs_at_default") {
2277
- const defaultInfo = this.defaultInfoForGroup.get(groupKey);
2278
- if (defaultInfo) {
2279
- return defaultInfo;
2280
- }
2281
- }
2282
- return this.scenarioInfo.get(scenario.key);
2283
- }
2284
- setDefaultScenarioInfoForGroup(groupKey, scenarioInfo) {
2285
- this.defaultInfoForGroup.set(groupKey, scenarioInfo);
2286
- }
2287
- addScenario(scenario, scenarioInfo, groupInfo) {
2288
- this.scenarios.set(scenario.key, scenario);
2289
- if (!scenarioInfo) {
2290
- scenarioInfo = this.getInfoForScenario(scenario);
2291
- }
2292
- this.scenarioInfo.set(scenario.key, scenarioInfo);
2293
- if (!groupInfo) {
2294
- groupInfo = this.getGroupInfoForScenario(scenario);
2295
- }
2296
- this.groupInfo.set(scenario.groupKey, groupInfo);
2297
- }
2298
- addScenarioMatrix() {
2299
- const inputVarIdsL = Array.from(this.bundleL.modelSpec.inputVars.keys());
2300
- const inputVarIdsR = Array.from(this.bundleR.modelSpec.inputVars.keys());
2301
- const inputVarIds = /* @__PURE__ */ new Set([...inputVarIdsL, ...inputVarIdsR]);
2302
- this.addScenario(allInputsAtPositionScenario("at-default"));
2303
- this.addScenario(allInputsAtPositionScenario("at-minimum"));
2304
- this.addScenario(allInputsAtPositionScenario("at-maximum"));
2305
- for (const inputVarId of inputVarIds) {
2306
- this.addScenario(inputAtPositionScenario(inputVarId, inputVarId, "at-minimum"));
2307
- this.addScenario(inputAtPositionScenario(inputVarId, inputVarId, "at-maximum"));
2308
- }
2309
- }
2310
- getGroupInfoForScenario(scenario) {
2311
- switch (scenario.kind) {
2312
- case "all-inputs":
2313
- return {
2314
- title: "All Inputs",
2315
- relatedItems: []
2316
- };
2317
- case "settings":
2318
- if (scenario.settings.length === 1) {
2319
- const inputVar = this.getInputVarForSetting(scenario.settings[0]);
2320
- let relatedItems;
2321
- let subtitle;
2322
- if (inputVar == null ? void 0 : inputVar.relatedItem) {
2323
- relatedItems = [inputVar.relatedItem];
2324
- subtitle = inputVar.relatedItem.locationPath.join('&nbsp;<span class="related-sep">&gt;</span>&nbsp;');
2325
- } else {
2326
- relatedItems = [];
2327
- subtitle = void 0;
2328
- }
2329
- return {
2330
- title: (inputVar == null ? void 0 : inputVar.varName) || "Unknown Input",
2331
- subtitle,
2332
- relatedItems
2333
- };
2334
- } else {
2335
- return {
2336
- title: "Multiple Inputs",
2337
- relatedItems: this.getRelatedItemsForSettings(scenario.settings)
2338
- };
2339
- }
2340
- default:
2341
- assertNever8(scenario);
3557
+ // src/comparison/config/comparison-scenarios.ts
3558
+ function getComparisonScenarios(scenarios) {
3559
+ return new ComparisonScenariosImpl(scenarios);
3560
+ }
3561
+ var ComparisonScenariosImpl = class {
3562
+ constructor(scenarios) {
3563
+ this.scenarioDefs = /* @__PURE__ */ new Map();
3564
+ for (const scenario of scenarios) {
3565
+ this.scenarioDefs.set(scenario.key, scenario);
2342
3566
  }
2343
3567
  }
2344
- getInfoForScenario(scenario) {
2345
- switch (scenario.kind) {
2346
- case "all-inputs":
2347
- return this.getInfoForPositionSetting(void 0, scenario.position);
2348
- case "settings":
2349
- if (scenario.settings.length === 1 && scenario.settings[0].kind === "position") {
2350
- const setting = scenario.settings[0];
2351
- return this.getInfoForPositionSetting(setting.inputVarId, setting.position);
2352
- } else {
2353
- return {
2354
- title: `PLACEHOLDER (scenario info not provided)`,
2355
- position: 1
2356
- };
2357
- }
2358
- default:
2359
- assertNever8(scenario);
2360
- }
3568
+ /**
3569
+ * Return all `ComparisonScenario` instances that are available for comparisons.
3570
+ */
3571
+ getAllScenarios() {
3572
+ return this.scenarioDefs.values();
2361
3573
  }
2362
- getInfoForPositionSetting(inputVarId, inputPosition2) {
2363
- const title = inputPosition2.replace("-", " ");
2364
- let subtitle;
2365
- if (inputVarId) {
2366
- const inputVarL = this.bundleL.modelSpec.inputVars.get(inputVarId);
2367
- const inputVarR = this.bundleR.modelSpec.inputVars.get(inputVarId);
2368
- const valueL = inputValue(inputVarL, inputPosition2);
2369
- const valueR = inputValue(inputVarR, inputPosition2);
2370
- if (valueL !== valueR) {
2371
- let values = "";
2372
- values += "(";
2373
- values += `<span class='dataset-color-0'>${valueL}</span>`;
2374
- values += "&nbsp;|&nbsp;";
2375
- values += `<span class='dataset-color-1'>${valueR}</span>`;
2376
- values += ")";
2377
- subtitle = values;
3574
+ /**
3575
+ * Return the scenario definition for the given key.
3576
+ *
3577
+ * @param key The key for the scenario.
3578
+ */
3579
+ getScenario(key) {
3580
+ return this.scenarioDefs.get(key);
3581
+ }
3582
+ };
3583
+
3584
+ // src/config/synchronized-model.ts
3585
+ function synchronizedBundleModel(sourceModel) {
3586
+ const promiseQueue = new PromiseQueue();
3587
+ return {
3588
+ modelSpec: sourceModel.modelSpec,
3589
+ getDatasetsForScenario: (scenarioSpec, datasetKeys) => {
3590
+ return promiseQueue.add(() => sourceModel.getDatasetsForScenario(scenarioSpec, datasetKeys));
3591
+ },
3592
+ getGraphDataForScenario: (scenarioSpec, graphId) => {
3593
+ return promiseQueue.add(() => sourceModel.getGraphDataForScenario(scenarioSpec, graphId));
3594
+ },
3595
+ getGraphLinksForScenario: sourceModel.getGraphLinksForScenario.bind(sourceModel)
3596
+ };
3597
+ }
3598
+ var PromiseQueue = class {
3599
+ constructor() {
3600
+ this.tasks = [];
3601
+ this.runningCount = 0;
3602
+ }
3603
+ add(f) {
3604
+ return new Promise((resolve, reject) => {
3605
+ const run = () => __async(this, null, function* () {
3606
+ this.runningCount++;
3607
+ const promise = f();
3608
+ try {
3609
+ const result = yield promise;
3610
+ resolve(result);
3611
+ } catch (e) {
3612
+ reject(e);
3613
+ } finally {
3614
+ this.runningCount--;
3615
+ this.runNext();
3616
+ }
3617
+ });
3618
+ if (this.runningCount < 1) {
3619
+ run();
2378
3620
  } else {
2379
- subtitle = `(${valueL})`;
3621
+ this.tasks.push(run);
2380
3622
  }
2381
- } else {
2382
- subtitle = void 0;
2383
- }
2384
- let position;
2385
- switch (inputPosition2) {
2386
- case "at-default":
2387
- position = 0;
2388
- break;
2389
- case "at-minimum":
2390
- position = 1;
2391
- break;
2392
- case "at-maximum":
2393
- position = 2;
2394
- break;
2395
- default:
2396
- assertNever8(inputPosition2);
2397
- }
2398
- return {
2399
- title,
2400
- subtitle,
2401
- position
2402
- };
3623
+ });
2403
3624
  }
2404
- getRelatedItemsForSettings(settings) {
2405
- const relatedItems = [];
2406
- for (const setting of settings) {
2407
- const inputVar = this.getInputVarForSetting(setting);
2408
- if (inputVar == null ? void 0 : inputVar.relatedItem) {
2409
- relatedItems.push(inputVar.relatedItem);
3625
+ runNext() {
3626
+ if (this.tasks.length > 0) {
3627
+ const task = this.tasks.shift();
3628
+ if (task) {
3629
+ task();
2410
3630
  }
2411
3631
  }
2412
- return relatedItems;
2413
- }
2414
- getInputVarForSetting(setting) {
2415
- const inputVarId = setting.inputVarId;
2416
- const inputVarL = this.bundleL.modelSpec.inputVars.get(inputVarId);
2417
- const inputVarR = this.bundleR.modelSpec.inputVars.get(inputVarId);
2418
- return inputVarR || inputVarL;
2419
3632
  }
2420
3633
  };
2421
- function inputValue(inputVar, position) {
2422
- if (inputVar) {
2423
- switch (position) {
2424
- case "at-default":
2425
- return inputVar.defaultValue.toString();
2426
- case "at-minimum":
2427
- return inputVar.minValue.toString();
2428
- case "at-maximum":
2429
- return inputVar.maxValue.toString();
2430
- default:
2431
- assertNever8(position);
3634
+
3635
+ // src/config/config.ts
3636
+ function createConfig(options) {
3637
+ return __async(this, null, function* () {
3638
+ var _a;
3639
+ const origCurrentBundle = yield loadSynchronized(options.current);
3640
+ let currentBundle;
3641
+ let comparisonConfig;
3642
+ if (options.comparison === void 0) {
3643
+ currentBundle = origCurrentBundle;
3644
+ } else {
3645
+ const baselineBundle = yield loadSynchronized(options.comparison.baseline);
3646
+ const renamedDatasetKeys = (_a = options.comparison.datasets) == null ? void 0 : _a.renamedDatasetKeys;
3647
+ const invertedRenamedKeys = /* @__PURE__ */ new Map();
3648
+ renamedDatasetKeys == null ? void 0 : renamedDatasetKeys.forEach((newKey, oldKey) => {
3649
+ invertedRenamedKeys.set(newKey, oldKey);
3650
+ });
3651
+ const rightKeyForLeftKey = (leftKey) => {
3652
+ return (renamedDatasetKeys == null ? void 0 : renamedDatasetKeys.get(leftKey)) || leftKey;
3653
+ };
3654
+ const leftKeyForRightKey = (rightKey) => {
3655
+ return invertedRenamedKeys.get(rightKey) || rightKey;
3656
+ };
3657
+ const origBundleModelR = origCurrentBundle.model;
3658
+ const adjBundleModelR = {
3659
+ modelSpec: origBundleModelR.modelSpec,
3660
+ getDatasetsForScenario: (scenarioSpec, datasetKeys) => __async(this, null, function* () {
3661
+ const rightKeys = datasetKeys.map(rightKeyForLeftKey);
3662
+ const result = yield origBundleModelR.getDatasetsForScenario(scenarioSpec, rightKeys);
3663
+ const mapWithRightKeys = result.datasetMap;
3664
+ const mapWithLeftKeys = /* @__PURE__ */ new Map();
3665
+ for (const [rightKey, dataset] of mapWithRightKeys.entries()) {
3666
+ const leftKey = leftKeyForRightKey(rightKey);
3667
+ mapWithLeftKeys.set(leftKey, dataset);
3668
+ }
3669
+ return {
3670
+ datasetMap: mapWithLeftKeys,
3671
+ modelRunTime: result.modelRunTime
3672
+ };
3673
+ }),
3674
+ getGraphDataForScenario: origBundleModelR.getGraphDataForScenario.bind(origBundleModelR),
3675
+ getGraphLinksForScenario: origBundleModelR.getGraphLinksForScenario.bind(origBundleModelR)
3676
+ };
3677
+ currentBundle = __spreadProps(__spreadValues({}, origCurrentBundle), {
3678
+ model: adjBundleModelR
3679
+ });
3680
+ const modelSpecL = baselineBundle.model.modelSpec;
3681
+ 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);
3685
+ comparisonConfig = {
3686
+ bundleL: baselineBundle,
3687
+ bundleR: currentBundle,
3688
+ thresholds: options.comparison.thresholds,
3689
+ scenarios: getComparisonScenarios(comparisonDefs.scenarios),
3690
+ datasets: getComparisonDatasets(modelSpecL, modelSpecR, options.comparison.datasets),
3691
+ viewGroups: comparisonDefs.viewGroups
3692
+ };
2432
3693
  }
2433
- } else {
2434
- return "n/a";
2435
- }
3694
+ const checkConfig = {
3695
+ bundle: currentBundle,
3696
+ tests: options.check.tests
3697
+ };
3698
+ return {
3699
+ check: checkConfig,
3700
+ comparison: comparisonConfig
3701
+ };
3702
+ });
3703
+ }
3704
+ function loadSynchronized(sourceBundle) {
3705
+ return __async(this, null, function* () {
3706
+ const sourceModel = yield sourceBundle.bundle.initModel();
3707
+ const synchronizedModel = synchronizedBundleModel(sourceModel);
3708
+ return {
3709
+ name: sourceBundle.name,
3710
+ version: sourceBundle.bundle.version,
3711
+ model: synchronizedModel
3712
+ };
3713
+ });
2436
3714
  }
2437
3715
 
2438
3716
  // src/perf/perf-runner.ts
2439
- import { assertNever as assertNever9 } from "assert-never";
3717
+ import { assertNever as assertNever12 } from "assert-never";
2440
3718
 
2441
3719
  // src/perf/perf-stats.ts
2442
3720
  var PerfStats = class {
@@ -2480,26 +3758,26 @@ var PerfRunner = class {
2480
3758
  this.bundleModelL = bundleModelL;
2481
3759
  this.bundleModelR = bundleModelR;
2482
3760
  this.mode = mode;
2483
- const scenario = allInputsAtPositionScenario("at-default");
3761
+ const scenarioSpec = allInputsAtPositionSpec("at-default");
2484
3762
  this.taskQueue = new TaskQueue({
2485
- process: async (request) => {
3763
+ process: (request) => __async(this, null, function* () {
2486
3764
  switch (request.kind) {
2487
3765
  case "left": {
2488
- const result = await bundleModelL.getDatasetsForScenario(scenario, []);
3766
+ const result = yield bundleModelL.getDatasetsForScenario(scenarioSpec, []);
2489
3767
  return {
2490
3768
  runTimeL: result.modelRunTime
2491
3769
  };
2492
3770
  }
2493
3771
  case "right": {
2494
- const result = await bundleModelR.getDatasetsForScenario(scenario, []);
3772
+ const result = yield bundleModelR.getDatasetsForScenario(scenarioSpec, []);
2495
3773
  return {
2496
3774
  runTimeR: result.modelRunTime
2497
3775
  };
2498
3776
  }
2499
3777
  case "both": {
2500
- const [resultL, resultR] = await Promise.all([
2501
- bundleModelL.getDatasetsForScenario(scenario, []),
2502
- bundleModelR.getDatasetsForScenario(scenario, [])
3778
+ const [resultL, resultR] = yield Promise.all([
3779
+ bundleModelL.getDatasetsForScenario(scenarioSpec, []),
3780
+ bundleModelR.getDatasetsForScenario(scenarioSpec, [])
2503
3781
  ]);
2504
3782
  return {
2505
3783
  runTimeL: resultL.modelRunTime,
@@ -2507,9 +3785,9 @@ var PerfRunner = class {
2507
3785
  };
2508
3786
  }
2509
3787
  default:
2510
- assertNever9(request.kind);
3788
+ assertNever12(request.kind);
2511
3789
  }
2512
- }
3790
+ })
2513
3791
  });
2514
3792
  }
2515
3793
  start() {
@@ -2555,46 +3833,164 @@ var PerfRunner = class {
2555
3833
  }
2556
3834
  };
2557
3835
 
2558
- // src/suite/suite-runner.ts
2559
- import assertNever10 from "assert-never";
2560
-
2561
3836
  // src/data/data-planner.ts
2562
- var ScenarioTaskSet = class {
2563
- constructor(scenario) {
2564
- this.scenario = scenario;
2565
- this.modelTasks = /* @__PURE__ */ new Map();
2566
- this.modelImplTasks = /* @__PURE__ */ new Map();
2567
- this.requestKind = "check";
3837
+ var DataPlanner = class {
3838
+ /**
3839
+ * @param batchSize The maximum number of impl vars that can be fetched
3840
+ * with a single request; this is usually the same as the number of
3841
+ * normal model outputs.
3842
+ */
3843
+ constructor(batchSize) {
3844
+ this.batchSize = batchSize;
3845
+ /** Tasks that need to access both "left" and "right" models in parallel. */
3846
+ this.taskSetsLR = /* @__PURE__ */ new Map();
3847
+ /** Tasks that only need to access the "left" model. */
3848
+ this.taskSetsL = /* @__PURE__ */ new Map();
3849
+ /** Tasks that only need to access the "right" model. */
3850
+ this.taskSetsR = /* @__PURE__ */ new Map();
3851
+ this.complete = false;
2568
3852
  }
2569
- addTask(kind, datasetKey, dataFunc) {
2570
- if (kind === "compare") {
2571
- this.requestKind = "compare";
3853
+ /**
3854
+ * Add a request to the plan for the given scenario(s) and data task.
3855
+ *
3856
+ * @param scenarioSpecL The input scenario used to configure the "left" model, or undefined if no data
3857
+ * is needed from the left model.
3858
+ * @param scenarioSpecR The input scenario used to configure the "right" model, or undefined if no data
3859
+ * is needed from the right model.
3860
+ * @param datasetKey The key for the dataset to be fetched from each model for the given scenario.
3861
+ * @param dataAction The action to be performed with the fetched datasets.
3862
+ */
3863
+ addRequest(scenarioSpecL, scenarioSpecR, datasetKey, dataAction) {
3864
+ if (scenarioSpecL === void 0 && scenarioSpecR === void 0) {
3865
+ console.warn("WARNING: Both scenario specs are undefined for DataPlanner request, skipping");
3866
+ return;
3867
+ }
3868
+ let taskSetsMap;
3869
+ let uid;
3870
+ if (scenarioSpecL && scenarioSpecR) {
3871
+ taskSetsMap = this.taskSetsLR;
3872
+ uid = scenarioPairUid(scenarioSpecL, scenarioSpecR);
3873
+ } else if (scenarioSpecR) {
3874
+ taskSetsMap = this.taskSetsR;
3875
+ uid = scenarioSpecR.uid;
3876
+ } else {
3877
+ taskSetsMap = this.taskSetsL;
3878
+ uid = scenarioSpecL.uid;
2572
3879
  }
2573
- const dataTask = {
3880
+ let taskSet = taskSetsMap.get(uid);
3881
+ if (!taskSet) {
3882
+ taskSet = new DataTaskSet(scenarioSpecL, scenarioSpecR);
3883
+ taskSetsMap.set(uid, taskSet);
3884
+ }
3885
+ taskSet.addTask({
2574
3886
  datasetKey,
2575
- dataFunc
3887
+ dataAction
3888
+ });
3889
+ }
3890
+ /**
3891
+ * Build a plan that minimizes the number of data fetches needed.
3892
+ */
3893
+ buildPlan() {
3894
+ if (this.complete) {
3895
+ throw new Error("DataPlanner.buildPlan() can only be called once");
3896
+ }
3897
+ this.complete = true;
3898
+ const lKeyMappings = /* @__PURE__ */ new Map();
3899
+ const rKeyMappings = /* @__PURE__ */ new Map();
3900
+ for (const lrUid of this.taskSetsLR.keys()) {
3901
+ const [lUid, rUid] = lrUid.split("::");
3902
+ if (!lKeyMappings.has(lUid)) {
3903
+ lKeyMappings.set(lUid, lrUid);
3904
+ }
3905
+ if (!rKeyMappings.has(rUid)) {
3906
+ rKeyMappings.set(rUid, lrUid);
3907
+ }
3908
+ }
3909
+ function merge(taskSetsLR, taskSetsForSide, keyMappingsForSide) {
3910
+ for (const [keyForSide, taskSetForSide] of taskSetsForSide.entries()) {
3911
+ const lrKey = keyMappingsForSide.get(keyForSide);
3912
+ if (lrKey) {
3913
+ const taskSetLR = taskSetsLR.get(lrKey);
3914
+ taskSetLR.merge(taskSetForSide);
3915
+ taskSetsForSide.delete(keyForSide);
3916
+ }
3917
+ }
3918
+ }
3919
+ merge(this.taskSetsLR, this.taskSetsL, lKeyMappings);
3920
+ merge(this.taskSetsLR, this.taskSetsR, rKeyMappings);
3921
+ const requests = [];
3922
+ const batchSize = this.batchSize;
3923
+ function addRequests(taskSets) {
3924
+ for (const taskSet of taskSets) {
3925
+ requests.push(...taskSet.buildRequests(batchSize));
3926
+ }
3927
+ }
3928
+ addRequests(this.taskSetsLR.values());
3929
+ addRequests(this.taskSetsL.values());
3930
+ addRequests(this.taskSetsR.values());
3931
+ return {
3932
+ requests
2576
3933
  };
3934
+ }
3935
+ };
3936
+ var DataTaskSet = class {
3937
+ constructor(scenarioSpecL, scenarioSpecR) {
3938
+ this.scenarioSpecL = scenarioSpecL;
3939
+ this.scenarioSpecR = scenarioSpecR;
3940
+ this.modelTasks = /* @__PURE__ */ new Map();
3941
+ this.modelImplTasks = /* @__PURE__ */ new Map();
3942
+ }
3943
+ /**
3944
+ * Add a task that will be performed when the data is fetched for the scenario(s)
3945
+ * associated with this task set.
3946
+ *
3947
+ * @param dataTask A task that performs an action using the fetched dataset(s).
3948
+ */
3949
+ addTask(dataTask) {
2577
3950
  let taskMap;
2578
- if (datasetKey.startsWith("ModelImpl")) {
3951
+ if (dataTask.datasetKey.startsWith("ModelImpl")) {
2579
3952
  taskMap = this.modelImplTasks;
2580
3953
  } else {
2581
3954
  taskMap = this.modelTasks;
2582
3955
  }
2583
- let tasks = taskMap.get(datasetKey);
3956
+ let tasks = taskMap.get(dataTask.datasetKey);
2584
3957
  if (!tasks) {
2585
3958
  tasks = [];
2586
- taskMap.set(datasetKey, tasks);
3959
+ taskMap.set(dataTask.datasetKey, tasks);
2587
3960
  }
2588
3961
  tasks.push(dataTask);
2589
3962
  }
3963
+ /**
3964
+ * Add all tasks from the given set into this set.
3965
+ *
3966
+ * @param otherTaskSet The other task set that will be merged into this one.
3967
+ */
3968
+ merge(otherTaskSet) {
3969
+ for (const tasks of otherTaskSet.modelTasks.values()) {
3970
+ for (const task of tasks) {
3971
+ this.addTask(task);
3972
+ }
3973
+ }
3974
+ for (const tasks of otherTaskSet.modelImplTasks.values()) {
3975
+ for (const task of tasks) {
3976
+ this.addTask(task);
3977
+ }
3978
+ }
3979
+ }
3980
+ /**
3981
+ * Create one or more data requests that can be used to fetch data for the configured scenario(s).
3982
+ *
3983
+ * @param batchSize The maximum number of impl vars that can be fetched with a single request.
3984
+ * This is usually the same as the number of normal model outputs.
3985
+ */
2590
3986
  buildRequests(batchSize) {
2591
3987
  const dataRequests = [];
2592
3988
  if (this.modelTasks.size > 0) {
2593
3989
  const dataTasks = [];
2594
3990
  this.modelTasks.forEach((tasks) => dataTasks.push(...tasks));
2595
3991
  dataRequests.push({
2596
- kind: this.requestKind,
2597
- scenario: this.scenario,
3992
+ scenarioSpecL: this.scenarioSpecL,
3993
+ scenarioSpecR: this.scenarioSpecR,
2598
3994
  dataTasks
2599
3995
  });
2600
3996
  }
@@ -2607,8 +4003,8 @@ var ScenarioTaskSet = class {
2607
4003
  dataTasks.push(...this.modelImplTasks.get(datasetKey));
2608
4004
  }
2609
4005
  dataRequests.push({
2610
- kind: this.requestKind,
2611
- scenario: this.scenario,
4006
+ scenarioSpecL: this.scenarioSpecL,
4007
+ scenarioSpecR: this.scenarioSpecR,
2612
4008
  dataTasks
2613
4009
  });
2614
4010
  }
@@ -2616,29 +4012,11 @@ var ScenarioTaskSet = class {
2616
4012
  return dataRequests;
2617
4013
  }
2618
4014
  };
2619
- var DataPlanner = class {
2620
- constructor(batchSize) {
2621
- this.batchSize = batchSize;
2622
- this.scenarioTaskSets = /* @__PURE__ */ new Map();
2623
- }
2624
- addRequest(kind, scenario, datasetKey, dataFunc) {
2625
- let scenarioTaskSet = this.scenarioTaskSets.get(scenario.key);
2626
- if (!scenarioTaskSet) {
2627
- scenarioTaskSet = new ScenarioTaskSet(scenario);
2628
- this.scenarioTaskSets.set(scenario.key, scenarioTaskSet);
2629
- }
2630
- scenarioTaskSet.addTask(kind, datasetKey, dataFunc);
2631
- }
2632
- buildPlan() {
2633
- const requests = [];
2634
- for (const taskSet of this.scenarioTaskSets.values()) {
2635
- requests.push(...taskSet.buildRequests(this.batchSize));
2636
- }
2637
- return {
2638
- requests
2639
- };
2640
- }
2641
- };
4015
+ function scenarioPairUid(scenarioSpecL, scenarioSpecR) {
4016
+ const uidL = (scenarioSpecL == null ? void 0 : scenarioSpecL.uid) || "";
4017
+ const uidR = (scenarioSpecR == null ? void 0 : scenarioSpecR.uid) || "";
4018
+ return `${uidL}::${uidR}`;
4019
+ }
2642
4020
 
2643
4021
  // src/check/check-runner.ts
2644
4022
  function runChecks(checkConfig, checkSpec, dataPlanner, refDataPlanner, simplifyScenarios) {
@@ -2648,7 +4026,7 @@ function runChecks(checkConfig, checkSpec, dataPlanner, refDataPlanner, simplify
2648
4026
  const checkPlan = checkPlanner.buildPlan();
2649
4027
  const refDatasets = /* @__PURE__ */ new Map();
2650
4028
  for (const [dataRefKey, dataRef] of checkPlan.dataRefs.entries()) {
2651
- refDataPlanner.addRequest("check", dataRef.scenario.scenario, dataRef.dataset.datasetKey, (datasets) => {
4029
+ refDataPlanner.addRequest(void 0, dataRef.scenario.spec, dataRef.dataset.datasetKey, (datasets) => {
2652
4030
  const dataset = datasets.datasetR;
2653
4031
  if (dataset) {
2654
4032
  refDatasets.set(dataRefKey, dataset);
@@ -2657,7 +4035,7 @@ function runChecks(checkConfig, checkSpec, dataPlanner, refDataPlanner, simplify
2657
4035
  }
2658
4036
  const checkResults = /* @__PURE__ */ new Map();
2659
4037
  for (const [checkKey, checkTask] of checkPlan.tasks.entries()) {
2660
- dataPlanner.addRequest("check", checkTask.scenario.scenario, checkTask.dataset.datasetKey, (datasets) => {
4038
+ dataPlanner.addRequest(void 0, checkTask.scenario.spec, checkTask.dataset.datasetKey, (datasets) => {
2661
4039
  const dataset = datasets.datasetR;
2662
4040
  const checkResult = runCheck(checkTask, dataset, refDatasets);
2663
4041
  checkResults.set(checkKey, checkResult);
@@ -2688,7 +4066,7 @@ function runCheck(checkTask, dataset, refDatasets) {
2688
4066
  name: dataRef.dataset.name
2689
4067
  }
2690
4068
  };
2691
- } else if (dataRef.scenario.scenario === void 0) {
4069
+ } else if (dataRef.scenario.spec === void 0) {
2692
4070
  if (dataRef.scenario.error) {
2693
4071
  return {
2694
4072
  status: "error",
@@ -2725,16 +4103,15 @@ function runCheck(checkTask, dataset, refDatasets) {
2725
4103
  return checkTask.action.run(dataset, opRefDatasets);
2726
4104
  }
2727
4105
 
2728
- // src/compare/compare-runner.ts
2729
- function runCompare(compareConfig, dataPlanner, simplifyScenarios) {
2730
- const scenarios = simplifyScenarios ? [allInputsAtPositionScenario("at-default")] : compareConfig.scenarios.getScenarios();
2731
- const datasetReports = [];
2732
- for (const scenario of scenarios) {
2733
- const datasetKeys = compareConfig.datasets.getDatasetKeysForScenario(scenario);
4106
+ // src/comparison/run/comparison-runner.ts
4107
+ function runComparisons(comparisonConfig, dataPlanner) {
4108
+ const testReports = [];
4109
+ for (const scenario of comparisonConfig.scenarios.getAllScenarios()) {
4110
+ const datasetKeys = comparisonConfig.datasets.getDatasetKeysForScenario(scenario);
2734
4111
  for (const datasetKey of datasetKeys) {
2735
- dataPlanner.addRequest("compare", scenario, datasetKey, (datasets) => {
4112
+ dataPlanner.addRequest(scenario.specL, scenario.specR, datasetKey, (datasets) => {
2736
4113
  const diffReport = diffDatasets(datasets.datasetL, datasets.datasetR);
2737
- datasetReports.push({
4114
+ testReports.push({
2738
4115
  scenarioKey: scenario.key,
2739
4116
  datasetKey,
2740
4117
  diffReport
@@ -2743,7 +4120,7 @@ function runCompare(compareConfig, dataPlanner, simplifyScenarios) {
2743
4120
  }
2744
4121
  }
2745
4122
  return () => {
2746
- return datasetReports;
4123
+ return testReports;
2747
4124
  };
2748
4125
  }
2749
4126
 
@@ -2781,9 +4158,9 @@ var SuiteRunner = class {
2781
4158
  const checkSpec = checkSpecResult.value;
2782
4159
  const simplifyScenarios = (options == null ? void 0 : options.simplifyScenarios) === true;
2783
4160
  const buildCheckReport2 = runChecks(this.config.check, checkSpec, dataPlanner, refDataPlanner, simplifyScenarios);
2784
- let buildCompareDatasetReports;
2785
- if (this.config.compare) {
2786
- buildCompareDatasetReports = runCompare(this.config.compare, dataPlanner, simplifyScenarios);
4161
+ let buildComparisonTestReports;
4162
+ if (this.config.comparison) {
4163
+ buildComparisonTestReports = runComparisons(this.config.comparison, dataPlanner);
2787
4164
  }
2788
4165
  this.taskQueue.onIdle = (error) => {
2789
4166
  var _a2, _b2, _c2, _d2;
@@ -2794,17 +4171,17 @@ var SuiteRunner = class {
2794
4171
  (_b2 = (_a2 = this.callbacks).onError) == null ? void 0 : _b2.call(_a2, error);
2795
4172
  } else {
2796
4173
  const checkReport = buildCheckReport2();
2797
- let compareReport;
2798
- if (this.config.compare) {
2799
- compareReport = {
2800
- datasetReports: buildCompareDatasetReports(),
4174
+ let comparisonReport;
4175
+ if (this.config.comparison) {
4176
+ comparisonReport = {
4177
+ testReports: buildComparisonTestReports(),
2801
4178
  perfReportL: this.perfStatsL.toReport(),
2802
4179
  perfReportR: this.perfStatsR.toReport()
2803
4180
  };
2804
4181
  }
2805
4182
  (_d2 = (_c2 = this.callbacks).onComplete) == null ? void 0 : _d2.call(_c2, {
2806
4183
  checkReport,
2807
- compareReport
4184
+ comparisonReport
2808
4185
  });
2809
4186
  }
2810
4187
  };
@@ -2813,10 +4190,10 @@ var SuiteRunner = class {
2813
4190
  const dataRequests = [...refDataPlan.requests, ...dataPlan.requests];
2814
4191
  const taskCount = dataRequests.length;
2815
4192
  if (taskCount === 0) {
2816
- let compareReport;
2817
- if (this.config.compare) {
2818
- compareReport = {
2819
- datasetReports: [],
4193
+ let comparisonReport;
4194
+ if (this.config.comparison) {
4195
+ comparisonReport = {
4196
+ testReports: [],
2820
4197
  perfReportL: this.perfStatsL.toReport(),
2821
4198
  perfReportR: this.perfStatsR.toReport()
2822
4199
  };
@@ -2827,7 +4204,7 @@ var SuiteRunner = class {
2827
4204
  checkReport: {
2828
4205
  groups: []
2829
4206
  },
2830
- compareReport
4207
+ comparisonReport
2831
4208
  });
2832
4209
  return;
2833
4210
  }
@@ -2841,51 +4218,46 @@ var SuiteRunner = class {
2841
4218
  });
2842
4219
  }
2843
4220
  }
2844
- async processRequest(request) {
2845
- const datasetKeySet = /* @__PURE__ */ new Set();
2846
- for (const dataTask of request.dataTasks) {
2847
- datasetKeySet.add(dataTask.datasetKey);
2848
- }
2849
- const datasetKeys = [...datasetKeySet];
2850
- const scenario = request.scenario;
2851
- let datasetsResultL;
2852
- let datasetsResultR;
2853
- switch (request.kind) {
2854
- case "check": {
2855
- const bundleModel = this.config.check.bundle.model;
2856
- datasetsResultR = await bundleModel.getDatasetsForScenario(scenario, datasetKeys);
2857
- break;
4221
+ processRequest(request) {
4222
+ return __async(this, null, function* () {
4223
+ var _a, _b;
4224
+ const datasetKeySet = /* @__PURE__ */ new Set();
4225
+ for (const dataTask of request.dataTasks) {
4226
+ datasetKeySet.add(dataTask.datasetKey);
2858
4227
  }
2859
- case "compare": {
2860
- const bundleModelL = this.config.compare.bundleL.model;
2861
- const bundleModelR = this.config.compare.bundleR.model;
2862
- const [resultL, resultR] = await Promise.all([
2863
- bundleModelL.getDatasetsForScenario(scenario, datasetKeys),
2864
- bundleModelR.getDatasetsForScenario(scenario, datasetKeys)
2865
- ]);
2866
- datasetsResultL = resultL;
2867
- datasetsResultR = resultR;
2868
- break;
4228
+ const datasetKeys = [...datasetKeySet];
4229
+ function getDatasets(bundleModel, scenarioSpec) {
4230
+ return __async(this, null, function* () {
4231
+ if (bundleModel && scenarioSpec) {
4232
+ return bundleModel.getDatasetsForScenario(scenarioSpec, datasetKeys);
4233
+ } else {
4234
+ return void 0;
4235
+ }
4236
+ });
2869
4237
  }
2870
- default:
2871
- assertNever10(request.kind);
2872
- }
2873
- if (datasetsResultL == null ? void 0 : datasetsResultL.modelRunTime) {
2874
- this.perfStatsL.addRun(datasetsResultL == null ? void 0 : datasetsResultL.modelRunTime);
2875
- }
2876
- if (datasetsResultR == null ? void 0 : datasetsResultR.modelRunTime) {
2877
- this.perfStatsR.addRun(datasetsResultR == null ? void 0 : datasetsResultR.modelRunTime);
2878
- }
2879
- const datasetMapL = datasetsResultL == null ? void 0 : datasetsResultL.datasetMap;
2880
- const datasetMapR = datasetsResultR == null ? void 0 : datasetsResultR.datasetMap;
2881
- for (const dataTask of request.dataTasks) {
2882
- const datasetL = datasetMapL == null ? void 0 : datasetMapL.get(dataTask.datasetKey);
2883
- const datasetR = datasetMapR == null ? void 0 : datasetMapR.get(dataTask.datasetKey);
2884
- dataTask.dataFunc({
2885
- datasetL,
2886
- datasetR
2887
- });
2888
- }
4238
+ const bundleModelL = (_a = this.config.comparison) == null ? void 0 : _a.bundleL.model;
4239
+ const bundleModelR = ((_b = this.config.comparison) == null ? void 0 : _b.bundleR.model) || this.config.check.bundle.model;
4240
+ const [datasetsResultL, datasetsResultR] = yield Promise.all([
4241
+ getDatasets(bundleModelL, request.scenarioSpecL),
4242
+ getDatasets(bundleModelR, request.scenarioSpecR)
4243
+ ]);
4244
+ if (datasetsResultL == null ? void 0 : datasetsResultL.modelRunTime) {
4245
+ this.perfStatsL.addRun(datasetsResultL == null ? void 0 : datasetsResultL.modelRunTime);
4246
+ }
4247
+ if (datasetsResultR == null ? void 0 : datasetsResultR.modelRunTime) {
4248
+ this.perfStatsR.addRun(datasetsResultR == null ? void 0 : datasetsResultR.modelRunTime);
4249
+ }
4250
+ const datasetMapL = datasetsResultL == null ? void 0 : datasetsResultL.datasetMap;
4251
+ const datasetMapR = datasetsResultR == null ? void 0 : datasetsResultR.datasetMap;
4252
+ for (const dataTask of request.dataTasks) {
4253
+ const datasetL = datasetMapL == null ? void 0 : datasetMapL.get(dataTask.datasetKey);
4254
+ const datasetR = datasetMapR == null ? void 0 : datasetMapR.get(dataTask.datasetKey);
4255
+ dataTask.dataAction({
4256
+ datasetL,
4257
+ datasetR
4258
+ });
4259
+ }
4260
+ });
2889
4261
  }
2890
4262
  };
2891
4263
  function runSuite(config, callbacks, options) {
@@ -2896,43 +4268,34 @@ function runSuite(config, callbacks, options) {
2896
4268
  };
2897
4269
  }
2898
4270
 
2899
- // src/suite/suite-summary.ts
4271
+ // src/suite/suite-reporting.ts
2900
4272
  function suiteSummaryFromReport(suiteReport) {
2901
4273
  const checkSummary = checkSummaryFromReport(suiteReport.checkReport);
2902
- let compareSummary;
2903
- if (suiteReport.compareReport) {
2904
- compareSummary = compareSummaryFromReport(suiteReport.compareReport);
4274
+ let comparisonSummary;
4275
+ if (suiteReport.comparisonReport) {
4276
+ comparisonSummary = comparisonSummaryFromReport(suiteReport.comparisonReport);
2905
4277
  }
2906
4278
  return {
2907
4279
  checkSummary,
2908
- compareSummary
4280
+ comparisonSummary
2909
4281
  };
2910
4282
  }
2911
4283
  export {
2912
4284
  CheckDataCoordinator,
2913
- CompareDataCoordinator,
2914
- DatasetManager,
4285
+ ComparisonDataCoordinator,
2915
4286
  PerfRunner,
2916
4287
  PerfStats,
2917
- ScenarioManager,
2918
- allInputsAtPositionScenario,
4288
+ categorizeComparisonTestSummaries,
2919
4289
  checkReportFromSummary,
2920
4290
  checkSummaryFromReport,
2921
- compareDatasets,
2922
- compareSummaryFromReport,
4291
+ comparisonSummaryFromReport,
2923
4292
  createConfig,
2924
4293
  datasetMessage,
2925
4294
  diffDatasets,
2926
4295
  diffGraphs,
2927
- inputAtPositionScenario,
2928
- inputAtValueScenario,
2929
- matrixScenarios,
2930
- positionSetting,
2931
4296
  predicateMessage,
2932
4297
  runSuite,
2933
4298
  scenarioMessage,
2934
- settingsScenario,
2935
- suiteSummaryFromReport,
2936
- valueSetting
4299
+ suiteSummaryFromReport
2937
4300
  };
2938
4301
  //# sourceMappingURL=index.js.map