@elabs-ai/components-process 4.2.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/dist/core/index.d.ts +801 -3
- package/dist/core/index.js +1334 -0
- package/dist/core/index.js.map +1 -1
- package/dist/index.d.ts +1889 -34
- package/dist/index.js +5512 -196
- package/dist/index.js.map +1 -1
- package/dist/test/index.d.ts +223 -5
- package/dist/test/index.js +346 -191
- package/dist/test/index.js.map +1 -1
- package/package.json +14 -13
- package/src/__contract__/case-table.contract.test.tsx +49 -0
- package/src/__contract__/compare-kpi-strip.contract.test.tsx +49 -0
- package/src/__contract__/conformance-overlay.contract.test.tsx +49 -0
- package/src/__contract__/happy-path-editor.contract.test.tsx +49 -0
- package/src/__contract__/violation-list.contract.test.tsx +49 -0
- package/src/abstraction-controls/abstraction-controls-per-type.test.tsx +80 -0
- package/src/abstraction-controls/abstraction-controls.stories.tsx +43 -1
- package/src/abstraction-controls/abstraction-controls.tsx +198 -5
- package/src/case-table/case-table.stories.tsx +89 -0
- package/src/case-table/case-table.test.tsx +148 -0
- package/src/case-table/case-table.tsx +144 -0
- package/src/case-table/columns.ts +116 -0
- package/src/case-table/index.ts +11 -0
- package/src/case-timeline/case-timeline-model.test.ts +72 -0
- package/src/case-timeline/case-timeline-model.ts +112 -0
- package/src/case-timeline/case-timeline.stories.tsx +94 -0
- package/src/case-timeline/case-timeline.test.tsx +51 -0
- package/src/case-timeline/case-timeline.tsx +109 -0
- package/src/case-timeline/index.ts +9 -0
- package/src/conformance-overlay/conformance-fixture.ts +59 -0
- package/src/conformance-overlay/conformance-legend.tsx +109 -0
- package/src/conformance-overlay/conformance-overlay.stories.tsx +116 -0
- package/src/conformance-overlay/conformance-overlay.test.tsx +88 -0
- package/src/conformance-overlay/conformance-overlay.tsx +107 -0
- package/src/conformance-overlay/conformance-state.test.ts +79 -0
- package/src/conformance-overlay/conformance-state.ts +220 -0
- package/src/conformance-overlay/index.ts +4 -0
- package/src/core/activity-color-scale.test.ts +107 -0
- package/src/core/activity-color-scale.ts +133 -0
- package/src/core/adapters/ocel.test.ts +112 -0
- package/src/core/adapters/ocel.ts +359 -0
- package/src/core/adapters/xes.test.ts +293 -0
- package/src/core/adapters/xes.ts +384 -0
- package/src/core/cases-from-log.test.ts +72 -0
- package/src/core/cases-from-log.ts +85 -0
- package/src/core/conformance.test.ts +80 -0
- package/src/core/conformance.ts +91 -0
- package/src/core/diff-graphs.test.ts +151 -0
- package/src/core/diff-graphs.ts +118 -0
- package/src/core/discover-object-centric-graph.test.ts +94 -0
- package/src/core/discover-object-centric-graph.ts +296 -0
- package/src/core/fixtures/ocel-sample.ts +82 -0
- package/src/core/fixtures/sample.xes +68 -0
- package/src/core/index.ts +113 -0
- package/src/core/reference-model.test.ts +43 -0
- package/src/core/reference-model.ts +116 -0
- package/src/core/replay-timeline.test.ts +161 -0
- package/src/core/replay-timeline.ts +260 -0
- package/src/core/segments.test.ts +185 -0
- package/src/core/segments.ts +153 -0
- package/src/core/token-replay.test.ts +218 -0
- package/src/core/token-replay.ts +456 -0
- package/src/core/types.ts +2 -2
- package/src/dotted-chart/compute-dots.test.ts +176 -0
- package/src/dotted-chart/compute-dots.ts +241 -0
- package/src/dotted-chart/dotted-chart-labels.ts +93 -0
- package/src/dotted-chart/dotted-chart.stories.tsx +182 -0
- package/src/dotted-chart/dotted-chart.test.tsx +135 -0
- package/src/dotted-chart/dotted-chart.tsx +841 -0
- package/src/dotted-chart/index.ts +23 -0
- package/src/dotted-chart/use-element-size.ts +33 -0
- package/src/happy-path-editor/happy-path-editor-context.ts +81 -0
- package/src/happy-path-editor/happy-path-editor.stories.tsx +116 -0
- package/src/happy-path-editor/happy-path-editor.test.tsx +142 -0
- package/src/happy-path-editor/happy-path-editor.tsx +239 -0
- package/src/happy-path-editor/happy-path-step-node.tsx +175 -0
- package/src/happy-path-editor/index.ts +4 -0
- package/src/index.ts +51 -1
- package/src/performance-spectrum/aggregate-segments.test.ts +107 -0
- package/src/performance-spectrum/aggregate-segments.ts +174 -0
- package/src/performance-spectrum/index.ts +25 -0
- package/src/performance-spectrum/performance-spectrum-context.tsx +116 -0
- package/src/performance-spectrum/performance-spectrum.stories.tsx +128 -0
- package/src/performance-spectrum/performance-spectrum.test.tsx +190 -0
- package/src/performance-spectrum/performance-spectrum.tsx +870 -0
- package/src/process-compare/compare-kpi-strip.stories.tsx +48 -0
- package/src/process-compare/compare-kpi-strip.tsx +94 -0
- package/src/process-compare/compare-model.ts +83 -0
- package/src/process-compare/compare-side.tsx +42 -0
- package/src/process-compare/diff-to-graph.ts +104 -0
- package/src/process-compare/index.ts +23 -0
- package/src/process-compare/process-compare.stories.tsx +184 -0
- package/src/process-compare/process-compare.test.tsx +224 -0
- package/src/process-compare/process-compare.tsx +251 -0
- package/src/process-explorer.stories.tsx +1 -1
- package/src/process-filter-bar/index.ts +2 -0
- package/src/process-filter-bar/process-filter-bar.stories.tsx +156 -0
- package/src/process-filter-bar/process-filter-bar.test.tsx +201 -0
- package/src/process-filter-bar/process-filter-bar.tsx +167 -0
- package/src/process-kpi-strip/process-kpi-strip.stories.tsx +47 -0
- package/src/process-kpi-strip/process-kpi-strip.test.tsx +67 -0
- package/src/process-kpi-strip/process-kpi-strip.tsx +148 -8
- package/src/process-map/activity-accent.ts +25 -0
- package/src/process-map/index.ts +1 -0
- package/src/process-map/map-model.test.ts +16 -0
- package/src/process-map/map-model.ts +323 -1
- package/src/process-map/object-centric-map.test.tsx +132 -0
- package/src/process-map/process-activity-node.tsx +152 -14
- package/src/process-map/process-map-object-centric.stories.tsx +219 -0
- package/src/process-map/process-map.stories.tsx +64 -0
- package/src/process-map/process-map.tsx +240 -16
- package/src/process-map/process-transition-edge.test.tsx +47 -0
- package/src/process-map/process-transition-edge.tsx +134 -7
- package/src/process-map/use-process-layout.ts +30 -9
- package/src/process-replay/congestion-heat.tsx +107 -0
- package/src/process-replay/index.ts +14 -0
- package/src/process-replay/process-replay.stories.tsx +168 -0
- package/src/process-replay/process-replay.test.tsx +170 -0
- package/src/process-replay/process-replay.tsx +285 -0
- package/src/process-replay/replay-controls.tsx +147 -0
- package/src/process-replay/replay-format.ts +83 -0
- package/src/process-replay/replay-tokens-context.ts +30 -0
- package/src/process-replay/use-controllable-value.ts +30 -0
- package/src/templates-process-explorer.stories.tsx +1304 -0
- package/src/test/contract.test.ts +66 -0
- package/src/test/contract.ts +107 -6
- package/src/test/doubles.test.tsx +87 -1
- package/src/test/doubles.tsx +174 -3
- package/src/test/index.ts +25 -1
- package/src/use-process-explorer/use-process-explorer.test.ts +44 -0
- package/src/use-process-explorer/use-process-explorer.ts +34 -2
- package/src/variant-explorer/coverage-bar.tsx +36 -0
- package/src/variant-explorer/index.ts +16 -0
- package/src/variant-explorer/sequence-chips.tsx +103 -0
- package/src/variant-explorer/variant-explorer-model.ts +42 -0
- package/src/variant-explorer/variant-explorer.stories.tsx +226 -0
- package/src/variant-explorer/variant-explorer.test.tsx +302 -0
- package/src/variant-explorer/variant-explorer.tsx +567 -0
- package/src/variant-explorer/variant-row.tsx +137 -0
- package/src/violation-list/index.ts +2 -0
- package/src/violation-list/violation-list.stories.tsx +73 -0
- package/src/violation-list/violation-list.test.tsx +84 -0
- package/src/violation-list/violation-list.tsx +259 -0
package/dist/core/index.js
CHANGED
|
@@ -1506,47 +1506,1381 @@ function createProcessWorker(options = {}) {
|
|
|
1506
1506
|
}
|
|
1507
1507
|
};
|
|
1508
1508
|
}
|
|
1509
|
+
|
|
1510
|
+
// src/core/activity-color-scale.ts
|
|
1511
|
+
var ACTIVITY_COLOR_SLOTS = 11;
|
|
1512
|
+
var ACTIVITY_OTHER_TOKEN = "--chart-12";
|
|
1513
|
+
var OTHER = Object.freeze({ token: ACTIVITY_OTHER_TOKEN, pattern: "other" });
|
|
1514
|
+
function alphanumerics(label) {
|
|
1515
|
+
return label.replace(/[^\p{L}\p{N}]/gu, "").toUpperCase();
|
|
1516
|
+
}
|
|
1517
|
+
function codeCandidates(label) {
|
|
1518
|
+
const out = [];
|
|
1519
|
+
const words = label.split(/[^\p{L}\p{N}]+/u).filter(Boolean);
|
|
1520
|
+
const first = words[0];
|
|
1521
|
+
const second = words[1];
|
|
1522
|
+
if (first && second) out.push(`${first[0]}${second[0]}`.toUpperCase());
|
|
1523
|
+
const letters = alphanumerics(label);
|
|
1524
|
+
const head = letters[0] ?? "?";
|
|
1525
|
+
if (letters.length >= 2) out.push(letters.slice(0, 2));
|
|
1526
|
+
for (let i = 2; i < letters.length; i += 1) out.push(`${head}${letters[i]}`);
|
|
1527
|
+
for (let digit = 1; digit <= 9; digit += 1) out.push(`${head}${digit}`);
|
|
1528
|
+
return out;
|
|
1529
|
+
}
|
|
1530
|
+
function uniqueCode(label, taken) {
|
|
1531
|
+
for (const candidate of codeCandidates(label)) {
|
|
1532
|
+
if (!taken.has(candidate)) return candidate;
|
|
1533
|
+
}
|
|
1534
|
+
for (let n = 0; n < 36 * 36; n += 1) {
|
|
1535
|
+
const candidate = n.toString(36).toUpperCase().padStart(2, "0");
|
|
1536
|
+
if (!taken.has(candidate)) return candidate;
|
|
1537
|
+
}
|
|
1538
|
+
return (alphanumerics(label).slice(0, 2) || "??").padEnd(2, "?");
|
|
1539
|
+
}
|
|
1540
|
+
function activityColorScale(graph) {
|
|
1541
|
+
const ranked = [...graph.activities].sort(
|
|
1542
|
+
(a, b) => b.cases - a.cases || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)
|
|
1543
|
+
);
|
|
1544
|
+
const colors = /* @__PURE__ */ new Map();
|
|
1545
|
+
const codes = /* @__PURE__ */ new Map();
|
|
1546
|
+
const labels = /* @__PURE__ */ new Map();
|
|
1547
|
+
const taken = /* @__PURE__ */ new Set();
|
|
1548
|
+
const legend = ranked.map((activity, index2) => {
|
|
1549
|
+
const color = index2 < ACTIVITY_COLOR_SLOTS ? { token: `--chart-${index2 + 1}` } : OTHER;
|
|
1550
|
+
const label = activity.label || activity.id;
|
|
1551
|
+
const code = uniqueCode(label, taken);
|
|
1552
|
+
taken.add(code);
|
|
1553
|
+
colors.set(activity.id, color);
|
|
1554
|
+
codes.set(activity.id, code);
|
|
1555
|
+
labels.set(activity.id, label);
|
|
1556
|
+
return { activityId: activity.id, label, code, ...color };
|
|
1557
|
+
});
|
|
1558
|
+
return {
|
|
1559
|
+
colorFor: (activityId) => colors.get(activityId) ?? OTHER,
|
|
1560
|
+
codeFor: (activityId) => codes.get(activityId) ?? (alphanumerics(activityId).slice(0, 2) || "??").padEnd(2, "?"),
|
|
1561
|
+
labelFor: (activityId) => labels.get(activityId) ?? activityId,
|
|
1562
|
+
legend
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
// src/core/cases-from-log.ts
|
|
1567
|
+
function toIso(ms) {
|
|
1568
|
+
return Number.isFinite(ms) ? new Date(ms).toISOString() : "";
|
|
1569
|
+
}
|
|
1570
|
+
function toCaseRow(kase, variantId2) {
|
|
1571
|
+
const row = {
|
|
1572
|
+
caseId: kase.caseId,
|
|
1573
|
+
start: toIso(kase.start),
|
|
1574
|
+
end: toIso(kase.end),
|
|
1575
|
+
durationMs: Number.isFinite(kase.duration) ? kase.duration : 0,
|
|
1576
|
+
eventCount: kase.events.length,
|
|
1577
|
+
variantId: variantId2
|
|
1578
|
+
};
|
|
1579
|
+
if (kase.attributes !== void 0) {
|
|
1580
|
+
row.attributes = kase.attributes;
|
|
1581
|
+
}
|
|
1582
|
+
return row;
|
|
1583
|
+
}
|
|
1584
|
+
function casesFromLog(log) {
|
|
1585
|
+
const normalized = asNormalizedLog(log);
|
|
1586
|
+
if (normalized.cases.length === 0) return [];
|
|
1587
|
+
const variantIdByCase = /* @__PURE__ */ new Map();
|
|
1588
|
+
for (const variant of extractVariants(normalized)) {
|
|
1589
|
+
for (const caseId of variant.caseIds) variantIdByCase.set(caseId, variant.id);
|
|
1590
|
+
}
|
|
1591
|
+
return normalized.cases.map((kase) => toCaseRow(kase, variantIdByCase.get(kase.caseId) ?? ""));
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
// src/core/reference-model.ts
|
|
1595
|
+
function placeName(index2) {
|
|
1596
|
+
return `p${index2}`;
|
|
1597
|
+
}
|
|
1598
|
+
function liftHappyPath(path) {
|
|
1599
|
+
const steps = path.steps;
|
|
1600
|
+
const places = [];
|
|
1601
|
+
for (let i = 0; i <= steps.length; i += 1) places.push(placeName(i));
|
|
1602
|
+
const transitions = [];
|
|
1603
|
+
steps.forEach((step, i) => {
|
|
1604
|
+
const before = placeName(i);
|
|
1605
|
+
const after = placeName(i + 1);
|
|
1606
|
+
transitions.push({
|
|
1607
|
+
id: `t${i}`,
|
|
1608
|
+
activity: step.activity,
|
|
1609
|
+
kind: "step",
|
|
1610
|
+
consumes: [before],
|
|
1611
|
+
produces: [after]
|
|
1612
|
+
});
|
|
1613
|
+
if (step.optional) {
|
|
1614
|
+
transitions.push({
|
|
1615
|
+
id: `skip${i}`,
|
|
1616
|
+
activity: step.activity,
|
|
1617
|
+
kind: "skip",
|
|
1618
|
+
consumes: [before],
|
|
1619
|
+
produces: [after]
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
if (step.repeatable) {
|
|
1623
|
+
transitions.push({
|
|
1624
|
+
id: `repeat${i}`,
|
|
1625
|
+
activity: step.activity,
|
|
1626
|
+
kind: "repeat",
|
|
1627
|
+
consumes: [after],
|
|
1628
|
+
produces: [after]
|
|
1629
|
+
});
|
|
1630
|
+
}
|
|
1631
|
+
});
|
|
1632
|
+
return {
|
|
1633
|
+
places,
|
|
1634
|
+
initialMarking: [placeName(0)],
|
|
1635
|
+
finalMarking: [placeName(steps.length)],
|
|
1636
|
+
transitions
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
// src/core/token-replay.ts
|
|
1641
|
+
var DEVIATION_TYPES = [
|
|
1642
|
+
"undesired",
|
|
1643
|
+
"skipped",
|
|
1644
|
+
"wrongOrder",
|
|
1645
|
+
"wrongStart",
|
|
1646
|
+
"incomplete"
|
|
1647
|
+
];
|
|
1648
|
+
var compiled = /* @__PURE__ */ new WeakMap();
|
|
1649
|
+
function pushTo(map, key, value) {
|
|
1650
|
+
const list = map.get(key);
|
|
1651
|
+
if (list === void 0) map.set(key, [value]);
|
|
1652
|
+
else list.push(value);
|
|
1653
|
+
}
|
|
1654
|
+
function compile(model) {
|
|
1655
|
+
const cached = compiled.get(model);
|
|
1656
|
+
if (cached !== void 0) return cached;
|
|
1657
|
+
const byActivity = /* @__PURE__ */ new Map();
|
|
1658
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
1659
|
+
const silentOutgoing = /* @__PURE__ */ new Map();
|
|
1660
|
+
const steps = [];
|
|
1661
|
+
const silent = model.transitions.filter((t) => t.kind === "skip");
|
|
1662
|
+
const visible = model.transitions.filter((t) => t.kind !== "skip");
|
|
1663
|
+
for (const t of [...visible].sort((a, b) => rank(a) - rank(b))) {
|
|
1664
|
+
pushTo(byActivity, t.activity, t);
|
|
1665
|
+
}
|
|
1666
|
+
for (const t of visible) if (t.kind === "step") steps.push(t);
|
|
1667
|
+
for (const t of [...silent, ...visible]) {
|
|
1668
|
+
if (t.consumes.length !== 1 || t.kind === "repeat") continue;
|
|
1669
|
+
const from = t.consumes[0];
|
|
1670
|
+
pushTo(outgoing, from, t);
|
|
1671
|
+
if (t.kind === "skip") pushTo(silentOutgoing, from, t);
|
|
1672
|
+
}
|
|
1673
|
+
const result = { model, byActivity, outgoing, silentOutgoing, steps };
|
|
1674
|
+
compiled.set(model, result);
|
|
1675
|
+
return result;
|
|
1676
|
+
}
|
|
1677
|
+
function rank(t) {
|
|
1678
|
+
return t.kind === "step" ? 0 : 1;
|
|
1679
|
+
}
|
|
1680
|
+
function tokens(marking, place) {
|
|
1681
|
+
return marking.get(place) ?? 0;
|
|
1682
|
+
}
|
|
1683
|
+
function isEnabled(marking, t) {
|
|
1684
|
+
const need = /* @__PURE__ */ new Map();
|
|
1685
|
+
for (const place of t.consumes) need.set(place, (need.get(place) ?? 0) + 1);
|
|
1686
|
+
for (const [place, count] of need) if (tokens(marking, place) < count) return false;
|
|
1687
|
+
return true;
|
|
1688
|
+
}
|
|
1689
|
+
function fire(marking, t) {
|
|
1690
|
+
for (const place of t.consumes) marking.set(place, tokens(marking, place) - 1);
|
|
1691
|
+
for (const place of t.produces) marking.set(place, tokens(marking, place) + 1);
|
|
1692
|
+
}
|
|
1693
|
+
function shortestChain(marking, target, edges) {
|
|
1694
|
+
if (tokens(marking, target) > 0) return [];
|
|
1695
|
+
const parent = /* @__PURE__ */ new Map();
|
|
1696
|
+
const queue = [];
|
|
1697
|
+
for (const [place, count] of marking) {
|
|
1698
|
+
if (count > 0 && !parent.has(place)) {
|
|
1699
|
+
parent.set(place, null);
|
|
1700
|
+
queue.push(place);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
for (let head = 0; head < queue.length; head += 1) {
|
|
1704
|
+
const place = queue[head];
|
|
1705
|
+
for (const t of edges.get(place) ?? []) {
|
|
1706
|
+
for (const next of t.produces) {
|
|
1707
|
+
if (parent.has(next)) continue;
|
|
1708
|
+
parent.set(next, t);
|
|
1709
|
+
if (next === target) return unwind(parent, target);
|
|
1710
|
+
queue.push(next);
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
return void 0;
|
|
1715
|
+
}
|
|
1716
|
+
function unwind(parent, target) {
|
|
1717
|
+
const chain = [];
|
|
1718
|
+
let at = parent.get(target);
|
|
1719
|
+
while (at !== null && at !== void 0) {
|
|
1720
|
+
chain.unshift(at);
|
|
1721
|
+
at = parent.get(at.consumes[0]);
|
|
1722
|
+
}
|
|
1723
|
+
return chain;
|
|
1724
|
+
}
|
|
1725
|
+
function enableSilently(cm, marking, places, counters) {
|
|
1726
|
+
const trial = new Map(marking);
|
|
1727
|
+
let produced = 0;
|
|
1728
|
+
let consumed = 0;
|
|
1729
|
+
for (const place of places) {
|
|
1730
|
+
if (tokens(trial, place) > 0) continue;
|
|
1731
|
+
const chain = shortestChain(trial, place, cm.silentOutgoing);
|
|
1732
|
+
if (chain === void 0) return false;
|
|
1733
|
+
for (const t of chain) {
|
|
1734
|
+
if (!isEnabled(trial, t)) return false;
|
|
1735
|
+
fire(trial, t);
|
|
1736
|
+
consumed += t.consumes.length;
|
|
1737
|
+
produced += t.produces.length;
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
for (const [place, count] of trial) marking.set(place, count);
|
|
1741
|
+
counters.produced += produced;
|
|
1742
|
+
counters.consumed += consumed;
|
|
1743
|
+
return true;
|
|
1744
|
+
}
|
|
1745
|
+
function expectedNext(cm, marking) {
|
|
1746
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
1747
|
+
const queue = [];
|
|
1748
|
+
for (const [place, count] of marking) {
|
|
1749
|
+
if (count > 0) {
|
|
1750
|
+
reachable.add(place);
|
|
1751
|
+
queue.push(place);
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
for (let head = 0; head < queue.length; head += 1) {
|
|
1755
|
+
for (const t of cm.silentOutgoing.get(queue[head]) ?? []) {
|
|
1756
|
+
for (const next of t.produces) {
|
|
1757
|
+
if (reachable.has(next)) continue;
|
|
1758
|
+
reachable.add(next);
|
|
1759
|
+
queue.push(next);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
for (const t of cm.steps) {
|
|
1764
|
+
if (t.consumes.every((place) => reachable.has(place))) return t.activity;
|
|
1765
|
+
}
|
|
1766
|
+
return void 0;
|
|
1767
|
+
}
|
|
1768
|
+
function fitnessOf(c) {
|
|
1769
|
+
const missingTerm = c.consumed > 0 ? 1 - c.missing / c.consumed : 1;
|
|
1770
|
+
const remainingTerm = c.produced > 0 ? 1 - c.remaining / c.produced : 1;
|
|
1771
|
+
return Math.min(1, Math.max(0, 0.5 * missingTerm + 0.5 * remainingTerm));
|
|
1772
|
+
}
|
|
1773
|
+
function replayActivities(caseId, trace, model) {
|
|
1774
|
+
const cm = compile(model);
|
|
1775
|
+
const marking = /* @__PURE__ */ new Map();
|
|
1776
|
+
const counters = { produced: 0, consumed: 0, missing: 0 };
|
|
1777
|
+
const deviations = [];
|
|
1778
|
+
const observed = new Set(trace);
|
|
1779
|
+
for (const place of model.initialMarking) {
|
|
1780
|
+
marking.set(place, tokens(marking, place) + 1);
|
|
1781
|
+
counters.produced += 1;
|
|
1782
|
+
}
|
|
1783
|
+
trace.forEach((activity, at) => {
|
|
1784
|
+
const candidates = cm.byActivity.get(activity);
|
|
1785
|
+
if (candidates === void 0 || candidates.length === 0) {
|
|
1786
|
+
deviations.push({ type: "undesired", activity, expected: expectedNext(cm, marking), at });
|
|
1787
|
+
counters.missing += 1;
|
|
1788
|
+
counters.consumed += 1;
|
|
1789
|
+
return;
|
|
1790
|
+
}
|
|
1791
|
+
const enabled = candidates.find((t) => isEnabled(marking, t)) ?? candidates.find((t) => enableSilently(cm, marking, t.consumes, counters));
|
|
1792
|
+
if (enabled !== void 0) {
|
|
1793
|
+
fire(marking, enabled);
|
|
1794
|
+
counters.consumed += enabled.consumes.length;
|
|
1795
|
+
counters.produced += enabled.produces.length;
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1798
|
+
const expected2 = expectedNext(cm, marking);
|
|
1799
|
+
let chosen = candidates[0];
|
|
1800
|
+
let chain;
|
|
1801
|
+
for (const t of candidates) {
|
|
1802
|
+
const gap = t.consumes.find((place) => tokens(marking, place) === 0);
|
|
1803
|
+
if (gap === void 0) continue;
|
|
1804
|
+
const found = shortestChain(marking, gap, cm.outgoing);
|
|
1805
|
+
if (found !== void 0 && (chain === void 0 || found.length < chain.length)) {
|
|
1806
|
+
chain = found;
|
|
1807
|
+
chosen = t;
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
if (at === 0) {
|
|
1811
|
+
deviations.push({ type: "wrongStart", activity, expected: expected2, at });
|
|
1812
|
+
} else {
|
|
1813
|
+
const unobserved = (chain ?? []).filter(
|
|
1814
|
+
(t) => t.kind === "step" && !observed.has(t.activity)
|
|
1815
|
+
);
|
|
1816
|
+
if (unobserved.length > 0) {
|
|
1817
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1818
|
+
for (const t of unobserved) {
|
|
1819
|
+
if (seen.has(t.activity)) continue;
|
|
1820
|
+
seen.add(t.activity);
|
|
1821
|
+
deviations.push({ type: "skipped", activity: t.activity, expected: t.activity, at });
|
|
1822
|
+
}
|
|
1823
|
+
} else {
|
|
1824
|
+
deviations.push({ type: "wrongOrder", activity, expected: expected2, at });
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
for (const place of chosen.consumes) {
|
|
1828
|
+
if (tokens(marking, place) === 0) {
|
|
1829
|
+
marking.set(place, 1);
|
|
1830
|
+
counters.missing += 1;
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
fire(marking, chosen);
|
|
1834
|
+
counters.consumed += chosen.consumes.length;
|
|
1835
|
+
counters.produced += chosen.produces.length;
|
|
1836
|
+
});
|
|
1837
|
+
enableSilently(cm, marking, model.finalMarking, counters);
|
|
1838
|
+
const expected = expectedNext(cm, marking);
|
|
1839
|
+
let reachedEnd = true;
|
|
1840
|
+
for (const place of model.finalMarking) {
|
|
1841
|
+
if (tokens(marking, place) > 0) marking.set(place, tokens(marking, place) - 1);
|
|
1842
|
+
else {
|
|
1843
|
+
counters.missing += 1;
|
|
1844
|
+
reachedEnd = false;
|
|
1845
|
+
}
|
|
1846
|
+
counters.consumed += 1;
|
|
1847
|
+
}
|
|
1848
|
+
if (!reachedEnd) {
|
|
1849
|
+
deviations.push({
|
|
1850
|
+
type: "incomplete",
|
|
1851
|
+
activity: trace.length > 0 ? trace[trace.length - 1] : void 0,
|
|
1852
|
+
expected,
|
|
1853
|
+
at: trace.length
|
|
1854
|
+
});
|
|
1855
|
+
}
|
|
1856
|
+
let remaining = 0;
|
|
1857
|
+
for (const count of marking.values()) remaining += count;
|
|
1858
|
+
const result = { caseId, ...counters, remaining };
|
|
1859
|
+
return { ...result, fitness: fitnessOf(result), deviations };
|
|
1860
|
+
}
|
|
1861
|
+
function activitiesOf(events) {
|
|
1862
|
+
return events.map((event) => event.activity);
|
|
1863
|
+
}
|
|
1864
|
+
function replayTrace(events, model) {
|
|
1865
|
+
const normalized = normalizeLog({ events });
|
|
1866
|
+
const instances = normalized.cases.flatMap((kase) => kase.events);
|
|
1867
|
+
if (normalized.cases.length > 1) instances.sort((a, b) => a.start - b.start);
|
|
1868
|
+
const caseId = normalized.cases[0]?.caseId ?? events[0]?.caseId ?? "";
|
|
1869
|
+
return replayActivities(caseId, activitiesOf(instances), model);
|
|
1870
|
+
}
|
|
1871
|
+
function emptyDeviationCounts() {
|
|
1872
|
+
return { undesired: 0, skipped: 0, wrongOrder: 0, wrongStart: 0, incomplete: 0 };
|
|
1873
|
+
}
|
|
1874
|
+
function tokenReplay(log, model) {
|
|
1875
|
+
const normalized = asNormalizedLog(log);
|
|
1876
|
+
const traces = [];
|
|
1877
|
+
const deviationCounts = emptyDeviationCounts();
|
|
1878
|
+
const perActivity = /* @__PURE__ */ Object.create(null);
|
|
1879
|
+
const perEdge = /* @__PURE__ */ Object.create(null);
|
|
1880
|
+
let fitnessSum = 0;
|
|
1881
|
+
for (const kase of normalized.cases) {
|
|
1882
|
+
const trace = activitiesOf(kase.events);
|
|
1883
|
+
const result = replayActivities(kase.caseId, trace, model);
|
|
1884
|
+
traces.push(result);
|
|
1885
|
+
fitnessSum += result.fitness;
|
|
1886
|
+
for (const deviation of result.deviations) {
|
|
1887
|
+
deviationCounts[deviation.type] += 1;
|
|
1888
|
+
if (deviation.activity !== void 0) {
|
|
1889
|
+
const entry = perActivity[deviation.activity] ??= { deviations: 0 };
|
|
1890
|
+
entry.deviations += 1;
|
|
1891
|
+
}
|
|
1892
|
+
if (deviation.at > 0 && deviation.at < trace.length) {
|
|
1893
|
+
const key = `${trace[deviation.at - 1]}${EDGE_KEY_SEPARATOR}${trace[deviation.at]}`;
|
|
1894
|
+
const entry = perEdge[key] ??= { deviations: 0 };
|
|
1895
|
+
entry.deviations += 1;
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
return {
|
|
1900
|
+
overallFitness: traces.length > 0 ? fitnessSum / traces.length : 0,
|
|
1901
|
+
traces,
|
|
1902
|
+
deviationCounts,
|
|
1903
|
+
perActivity,
|
|
1904
|
+
perEdge
|
|
1905
|
+
};
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
// src/core/conformance.ts
|
|
1909
|
+
var DAY_MS = 864e5;
|
|
1910
|
+
function bucketOf(ms, bucket) {
|
|
1911
|
+
const date = new Date(ms);
|
|
1912
|
+
if (bucket === "week") {
|
|
1913
|
+
const offset = (date.getUTCDay() + 6) % 7;
|
|
1914
|
+
const monday = new Date(
|
|
1915
|
+
Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()) - offset * DAY_MS
|
|
1916
|
+
);
|
|
1917
|
+
return monday.toISOString().slice(0, 10);
|
|
1918
|
+
}
|
|
1919
|
+
const iso = date.toISOString();
|
|
1920
|
+
return bucket === "month" ? iso.slice(0, 7) : iso.slice(0, 10);
|
|
1921
|
+
}
|
|
1922
|
+
function conformanceRateSeries(log, model, bucket) {
|
|
1923
|
+
const totals = /* @__PURE__ */ new Map();
|
|
1924
|
+
for (const kase of asNormalizedLog(log).cases) {
|
|
1925
|
+
if (!Number.isFinite(kase.start)) continue;
|
|
1926
|
+
const key = bucketOf(kase.start, bucket);
|
|
1927
|
+
const trace = kase.events.map((event) => event.activity);
|
|
1928
|
+
const { fitness } = replayActivities(kase.caseId, trace, model);
|
|
1929
|
+
const entry = totals.get(key);
|
|
1930
|
+
if (entry === void 0) totals.set(key, { sum: fitness, count: 1 });
|
|
1931
|
+
else {
|
|
1932
|
+
entry.sum += fitness;
|
|
1933
|
+
entry.count += 1;
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
return [...totals.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, { sum, count }]) => ({ bucket: key, fitness: sum / count, caseCount: count }));
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
// src/core/adapters/xes.ts
|
|
1940
|
+
var XmlSyntaxError = class extends Error {
|
|
1941
|
+
};
|
|
1942
|
+
var NAME_STOP = /[\s/>=]/;
|
|
1943
|
+
var ENTITY_NAMES = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'" };
|
|
1944
|
+
function decodeXmlEntities(text) {
|
|
1945
|
+
return text.replace(/&(#x[0-9a-fA-F]+|#\d+|[a-zA-Z]+);/g, (match, body) => {
|
|
1946
|
+
if (body[0] === "#") {
|
|
1947
|
+
const isHex = body[1] === "x" || body[1] === "X";
|
|
1948
|
+
const codePoint = Number.parseInt(body.slice(isHex ? 2 : 1), isHex ? 16 : 10);
|
|
1949
|
+
return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : match;
|
|
1950
|
+
}
|
|
1951
|
+
return ENTITY_NAMES[body] ?? match;
|
|
1952
|
+
});
|
|
1953
|
+
}
|
|
1954
|
+
function parseXmlDocument(text) {
|
|
1955
|
+
let i = 0;
|
|
1956
|
+
const n = text.length;
|
|
1957
|
+
const skipWhitespace = () => {
|
|
1958
|
+
while (i < n && /\s/.test(text[i])) i += 1;
|
|
1959
|
+
};
|
|
1960
|
+
const skipUntil = (marker, what) => {
|
|
1961
|
+
const at = text.indexOf(marker, i);
|
|
1962
|
+
if (at === -1) throw new XmlSyntaxError(`unterminated ${what}`);
|
|
1963
|
+
i = at + marker.length;
|
|
1964
|
+
};
|
|
1965
|
+
const readName = () => {
|
|
1966
|
+
const start = i;
|
|
1967
|
+
while (i < n && !NAME_STOP.test(text[i])) i += 1;
|
|
1968
|
+
if (i === start) throw new XmlSyntaxError(`expected a name at offset ${start}`);
|
|
1969
|
+
return text.slice(start, i);
|
|
1970
|
+
};
|
|
1971
|
+
const readAttrs = () => {
|
|
1972
|
+
const attrs = {};
|
|
1973
|
+
for (; ; ) {
|
|
1974
|
+
skipWhitespace();
|
|
1975
|
+
const c = text[i];
|
|
1976
|
+
if (c === void 0) throw new XmlSyntaxError("unexpected end of input in a start tag");
|
|
1977
|
+
if (c === "/" || c === ">") return attrs;
|
|
1978
|
+
const name = readName();
|
|
1979
|
+
skipWhitespace();
|
|
1980
|
+
if (text[i] !== "=") throw new XmlSyntaxError(`expected "=" after attribute "${name}"`);
|
|
1981
|
+
i += 1;
|
|
1982
|
+
skipWhitespace();
|
|
1983
|
+
const quote = text[i];
|
|
1984
|
+
if (quote !== '"' && quote !== "'") {
|
|
1985
|
+
throw new XmlSyntaxError(`expected a quoted value for attribute "${name}"`);
|
|
1986
|
+
}
|
|
1987
|
+
i += 1;
|
|
1988
|
+
const start = i;
|
|
1989
|
+
while (i < n && text[i] !== quote) i += 1;
|
|
1990
|
+
if (i >= n) throw new XmlSyntaxError(`unterminated value for attribute "${name}"`);
|
|
1991
|
+
attrs[name] = decodeXmlEntities(text.slice(start, i));
|
|
1992
|
+
i += 1;
|
|
1993
|
+
}
|
|
1994
|
+
};
|
|
1995
|
+
const readElement = () => {
|
|
1996
|
+
i += 1;
|
|
1997
|
+
const tag = readName();
|
|
1998
|
+
const attrs = readAttrs();
|
|
1999
|
+
skipWhitespace();
|
|
2000
|
+
if (text[i] === "/" && text[i + 1] === ">") {
|
|
2001
|
+
i += 2;
|
|
2002
|
+
return { tag, attrs, children: [] };
|
|
2003
|
+
}
|
|
2004
|
+
if (text[i] !== ">") throw new XmlSyntaxError(`expected ">" closing "<${tag}>"`);
|
|
2005
|
+
i += 1;
|
|
2006
|
+
const children = [];
|
|
2007
|
+
for (; ; ) {
|
|
2008
|
+
const lt = text.indexOf("<", i);
|
|
2009
|
+
if (lt === -1) throw new XmlSyntaxError(`unterminated element "<${tag}>"`);
|
|
2010
|
+
i = lt;
|
|
2011
|
+
if (text.startsWith("<!--", i)) {
|
|
2012
|
+
skipUntil("-->", "comment");
|
|
2013
|
+
continue;
|
|
2014
|
+
}
|
|
2015
|
+
if (text.startsWith("<![CDATA[", i)) {
|
|
2016
|
+
skipUntil("]]>", "CDATA section");
|
|
2017
|
+
continue;
|
|
2018
|
+
}
|
|
2019
|
+
if (text.startsWith("</", i)) {
|
|
2020
|
+
i += 2;
|
|
2021
|
+
const closeName = readName();
|
|
2022
|
+
skipWhitespace();
|
|
2023
|
+
if (text[i] !== ">") throw new XmlSyntaxError(`expected ">" closing "</${closeName}>"`);
|
|
2024
|
+
i += 1;
|
|
2025
|
+
if (closeName !== tag) {
|
|
2026
|
+
throw new XmlSyntaxError(
|
|
2027
|
+
`mismatched closing tag: expected "</${tag}>", found "</${closeName}>"`
|
|
2028
|
+
);
|
|
2029
|
+
}
|
|
2030
|
+
return { tag, attrs, children };
|
|
2031
|
+
}
|
|
2032
|
+
children.push(readElement());
|
|
2033
|
+
}
|
|
2034
|
+
};
|
|
2035
|
+
for (; ; ) {
|
|
2036
|
+
skipWhitespace();
|
|
2037
|
+
if (i >= n) throw new XmlSyntaxError("empty document");
|
|
2038
|
+
if (text.startsWith("<?", i)) {
|
|
2039
|
+
skipUntil("?>", "processing instruction");
|
|
2040
|
+
continue;
|
|
2041
|
+
}
|
|
2042
|
+
if (text.startsWith("<!--", i)) {
|
|
2043
|
+
skipUntil("-->", "comment");
|
|
2044
|
+
continue;
|
|
2045
|
+
}
|
|
2046
|
+
if (text.startsWith("<!", i)) {
|
|
2047
|
+
skipUntil(">", "declaration");
|
|
2048
|
+
continue;
|
|
2049
|
+
}
|
|
2050
|
+
break;
|
|
2051
|
+
}
|
|
2052
|
+
if (text[i] !== "<") throw new XmlSyntaxError("expected a root element");
|
|
2053
|
+
return readElement();
|
|
2054
|
+
}
|
|
2055
|
+
var XES_VALUE_TAGS = /* @__PURE__ */ new Set(["string", "date", "int", "float", "boolean"]);
|
|
2056
|
+
function readXesValue(element) {
|
|
2057
|
+
const raw = element.attrs.value;
|
|
2058
|
+
if (raw === void 0) return null;
|
|
2059
|
+
if (element.tag === "int") {
|
|
2060
|
+
const value = Number.parseInt(raw, 10);
|
|
2061
|
+
return Number.isFinite(value) ? value : raw;
|
|
2062
|
+
}
|
|
2063
|
+
if (element.tag === "float") {
|
|
2064
|
+
const value = Number.parseFloat(raw);
|
|
2065
|
+
return Number.isFinite(value) ? value : raw;
|
|
2066
|
+
}
|
|
2067
|
+
if (element.tag === "boolean") return raw.trim().toLowerCase() === "true";
|
|
2068
|
+
return raw;
|
|
2069
|
+
}
|
|
2070
|
+
function readAttributeChildren(children) {
|
|
2071
|
+
const result = {};
|
|
2072
|
+
for (const child of children) {
|
|
2073
|
+
if (!XES_VALUE_TAGS.has(child.tag)) continue;
|
|
2074
|
+
const key = child.attrs.key;
|
|
2075
|
+
if (key === void 0) continue;
|
|
2076
|
+
result[key] = readXesValue(child);
|
|
2077
|
+
}
|
|
2078
|
+
return result;
|
|
2079
|
+
}
|
|
2080
|
+
function withDefaults(own, defaults) {
|
|
2081
|
+
return defaults === void 0 ? own : { ...defaults, ...own };
|
|
2082
|
+
}
|
|
2083
|
+
function collectGlobalDefaults(root, scope) {
|
|
2084
|
+
let defaults;
|
|
2085
|
+
for (const child of root.children) {
|
|
2086
|
+
if (child.tag !== "global" || child.attrs.scope !== scope) continue;
|
|
2087
|
+
defaults = withDefaults(readAttributeChildren(child.children), defaults);
|
|
2088
|
+
}
|
|
2089
|
+
return defaults;
|
|
2090
|
+
}
|
|
2091
|
+
function resolveClassifierKeys(root, options) {
|
|
2092
|
+
if (options?.classifiers !== void 0 && options.classifiers.length > 0) {
|
|
2093
|
+
return options.classifiers;
|
|
2094
|
+
}
|
|
2095
|
+
const classifier = root.children.find((child) => child.tag === "classifier");
|
|
2096
|
+
const keys = classifier?.attrs.keys?.trim();
|
|
2097
|
+
if (keys !== void 0 && keys !== "") return keys.split(/\s+/);
|
|
2098
|
+
return ["concept:name"];
|
|
2099
|
+
}
|
|
2100
|
+
function mapLifecycle(raw, model) {
|
|
2101
|
+
if (typeof raw !== "string" || model === "none") return void 0;
|
|
2102
|
+
const value = raw.trim().toLowerCase();
|
|
2103
|
+
if (value === "") return void 0;
|
|
2104
|
+
return value === "start" ? "start" : "complete";
|
|
2105
|
+
}
|
|
2106
|
+
function fromXes(source, options) {
|
|
2107
|
+
let root;
|
|
2108
|
+
try {
|
|
2109
|
+
root = parseXmlDocument(source);
|
|
2110
|
+
} catch (error) {
|
|
2111
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2112
|
+
return { ok: false, errors: [{ type: "malformed_xml", message }] };
|
|
2113
|
+
}
|
|
2114
|
+
const lifecycleModel = options?.lifecycleModel ?? "standard";
|
|
2115
|
+
const classifierKeys = resolveClassifierKeys(root, options);
|
|
2116
|
+
const consumedKeys = /* @__PURE__ */ new Set([
|
|
2117
|
+
...classifierKeys,
|
|
2118
|
+
"time:timestamp",
|
|
2119
|
+
"org:resource",
|
|
2120
|
+
"lifecycle:transition"
|
|
2121
|
+
]);
|
|
2122
|
+
const eventDefaults = collectGlobalDefaults(root, "event");
|
|
2123
|
+
const traceDefaults = collectGlobalDefaults(root, "trace");
|
|
2124
|
+
const errors = [];
|
|
2125
|
+
const events = [];
|
|
2126
|
+
let caseAttributes;
|
|
2127
|
+
const traces = root.children.filter((child) => child.tag === "trace");
|
|
2128
|
+
traces.forEach((trace, traceIndex) => {
|
|
2129
|
+
const traceEvents = trace.children.filter((child) => child.tag === "event");
|
|
2130
|
+
if (traceEvents.length === 0) {
|
|
2131
|
+
errors.push({ type: "missing_trace", message: "trace has no events", traceIndex });
|
|
2132
|
+
return;
|
|
2133
|
+
}
|
|
2134
|
+
const { "concept:name": rawCaseId, ...traceValues } = withDefaults(
|
|
2135
|
+
readAttributeChildren(trace.children),
|
|
2136
|
+
traceDefaults
|
|
2137
|
+
);
|
|
2138
|
+
const caseId = typeof rawCaseId === "string" && rawCaseId !== "" ? rawCaseId : `trace-${traceIndex}`;
|
|
2139
|
+
if (Object.keys(traceValues).length > 0) {
|
|
2140
|
+
caseAttributes ??= {};
|
|
2141
|
+
caseAttributes[caseId] = traceValues;
|
|
2142
|
+
}
|
|
2143
|
+
traceEvents.forEach((eventElement, eventIndex) => {
|
|
2144
|
+
const values = withDefaults(readAttributeChildren(eventElement.children), eventDefaults);
|
|
2145
|
+
const activityParts = [];
|
|
2146
|
+
let missingActivityKey = false;
|
|
2147
|
+
for (const key of classifierKeys) {
|
|
2148
|
+
const value = values[key];
|
|
2149
|
+
if (value === void 0 || value === null) {
|
|
2150
|
+
missingActivityKey = true;
|
|
2151
|
+
break;
|
|
2152
|
+
}
|
|
2153
|
+
activityParts.push(String(value));
|
|
2154
|
+
}
|
|
2155
|
+
if (missingActivityKey) {
|
|
2156
|
+
errors.push({
|
|
2157
|
+
type: "missing_concept_name",
|
|
2158
|
+
message: "event is missing its classifier key(s)",
|
|
2159
|
+
traceIndex,
|
|
2160
|
+
eventIndex
|
|
2161
|
+
});
|
|
2162
|
+
return;
|
|
2163
|
+
}
|
|
2164
|
+
const rawTimestamp = values["time:timestamp"];
|
|
2165
|
+
if (rawTimestamp === void 0 || rawTimestamp === null) {
|
|
2166
|
+
errors.push({
|
|
2167
|
+
type: "missing_timestamp",
|
|
2168
|
+
message: "event is missing time:timestamp",
|
|
2169
|
+
traceIndex,
|
|
2170
|
+
eventIndex
|
|
2171
|
+
});
|
|
2172
|
+
return;
|
|
2173
|
+
}
|
|
2174
|
+
const row = {
|
|
2175
|
+
caseId,
|
|
2176
|
+
activity: activityParts.join("+"),
|
|
2177
|
+
timestamp: String(rawTimestamp)
|
|
2178
|
+
};
|
|
2179
|
+
const resource = values["org:resource"];
|
|
2180
|
+
if (typeof resource === "string" && resource !== "") row.resource = resource;
|
|
2181
|
+
const lifecycle = mapLifecycle(values["lifecycle:transition"], lifecycleModel);
|
|
2182
|
+
if (lifecycle !== void 0) row.lifecycle = lifecycle;
|
|
2183
|
+
const attributes = {};
|
|
2184
|
+
let anyAttribute = false;
|
|
2185
|
+
for (const [key, value] of Object.entries(values)) {
|
|
2186
|
+
if (consumedKeys.has(key)) continue;
|
|
2187
|
+
attributes[key] = value;
|
|
2188
|
+
anyAttribute = true;
|
|
2189
|
+
}
|
|
2190
|
+
if (anyAttribute) row.attributes = attributes;
|
|
2191
|
+
events.push(row);
|
|
2192
|
+
});
|
|
2193
|
+
});
|
|
2194
|
+
if (errors.length > 0) return { ok: false, errors };
|
|
2195
|
+
const log = { events };
|
|
2196
|
+
if (caseAttributes !== void 0) log.caseAttributes = caseAttributes;
|
|
2197
|
+
return { ok: true, log };
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
// src/core/segments.ts
|
|
2201
|
+
function segmentKey(from, to) {
|
|
2202
|
+
return `${from}${EDGE_KEY_SEPARATOR}${to}`;
|
|
2203
|
+
}
|
|
2204
|
+
function segmentsFor(log, order) {
|
|
2205
|
+
const wanted = /* @__PURE__ */ new Set();
|
|
2206
|
+
for (const def of order) wanted.add(segmentKey(def.from, def.to));
|
|
2207
|
+
const out = [];
|
|
2208
|
+
if (wanted.size === 0) return out;
|
|
2209
|
+
for (const kase of asNormalizedLog(log).cases) {
|
|
2210
|
+
const trace = kase.events;
|
|
2211
|
+
for (let i = 1; i < trace.length; i += 1) {
|
|
2212
|
+
const previous = trace[i - 1];
|
|
2213
|
+
const event = trace[i];
|
|
2214
|
+
const key = segmentKey(previous.activity, event.activity);
|
|
2215
|
+
if (!wanted.has(key)) continue;
|
|
2216
|
+
const start = previous.end;
|
|
2217
|
+
if (!Number.isFinite(start) || !Number.isFinite(event.start)) continue;
|
|
2218
|
+
const end = Math.max(start, event.start);
|
|
2219
|
+
out.push({ segment: key, caseId: kase.caseId, start, end, duration: end - start });
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
return out;
|
|
2223
|
+
}
|
|
2224
|
+
function segmentOrderByFrequency(graph, limit = Number.POSITIVE_INFINITY) {
|
|
2225
|
+
const n = Math.max(0, Math.floor(limit));
|
|
2226
|
+
return graph.transitions.slice(0, n).map((t) => ({ from: t.source, to: t.target }));
|
|
2227
|
+
}
|
|
2228
|
+
function segmentOrderForVariant(variant) {
|
|
2229
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2230
|
+
const out = [];
|
|
2231
|
+
for (let i = 1; i < variant.sequence.length; i += 1) {
|
|
2232
|
+
const from = variant.sequence[i - 1];
|
|
2233
|
+
const to = variant.sequence[i];
|
|
2234
|
+
const key = segmentKey(from, to);
|
|
2235
|
+
if (seen.has(key)) continue;
|
|
2236
|
+
seen.add(key);
|
|
2237
|
+
out.push({ from, to });
|
|
2238
|
+
}
|
|
2239
|
+
return out;
|
|
2240
|
+
}
|
|
2241
|
+
function durationQuartileThresholds(durations) {
|
|
2242
|
+
const sorted = [];
|
|
2243
|
+
for (const d of durations) if (Number.isFinite(d)) sorted.push(d);
|
|
2244
|
+
sorted.sort(ascending);
|
|
2245
|
+
if (sorted.length === 0) return [0, 0, 0];
|
|
2246
|
+
return [quantileSorted(sorted, 0.25), quantileSorted(sorted, 0.5), quantileSorted(sorted, 0.75)];
|
|
2247
|
+
}
|
|
2248
|
+
function quartileOf(duration, thresholds) {
|
|
2249
|
+
if (duration <= thresholds[0]) return 1;
|
|
2250
|
+
if (duration <= thresholds[1]) return 2;
|
|
2251
|
+
if (duration <= thresholds[2]) return 3;
|
|
2252
|
+
return 4;
|
|
2253
|
+
}
|
|
2254
|
+
function durationQuartile(occurrence, allDurationsForSegment) {
|
|
2255
|
+
return quartileOf(occurrence.duration, durationQuartileThresholds(allDurationsForSegment));
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
// src/core/diff-graphs.ts
|
|
2259
|
+
function transitionKey2(transition) {
|
|
2260
|
+
return `${transition.source}${EDGE_KEY_SEPARATOR}${transition.target}`;
|
|
2261
|
+
}
|
|
2262
|
+
function unionIds(first, second) {
|
|
2263
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2264
|
+
const out = [];
|
|
2265
|
+
for (const id of first) {
|
|
2266
|
+
if (seen.has(id)) continue;
|
|
2267
|
+
seen.add(id);
|
|
2268
|
+
out.push(id);
|
|
2269
|
+
}
|
|
2270
|
+
for (const id of second) {
|
|
2271
|
+
if (seen.has(id)) continue;
|
|
2272
|
+
seen.add(id);
|
|
2273
|
+
out.push(id);
|
|
2274
|
+
}
|
|
2275
|
+
return out;
|
|
2276
|
+
}
|
|
2277
|
+
function diffEntries(aById, bById, ids, referenceValue) {
|
|
2278
|
+
return ids.map((id) => {
|
|
2279
|
+
const a = aById.get(id);
|
|
2280
|
+
const b = bById.get(id);
|
|
2281
|
+
const state = a !== void 0 && b !== void 0 ? "common" : a ? "aOnly" : "bOnly";
|
|
2282
|
+
const entry = { id, state };
|
|
2283
|
+
if (a !== void 0) entry.a = a;
|
|
2284
|
+
if (b !== void 0) entry.b = b;
|
|
2285
|
+
if (a !== void 0 && b !== void 0) {
|
|
2286
|
+
const aValue = referenceValue(a);
|
|
2287
|
+
const bValue = referenceValue(b);
|
|
2288
|
+
entry.delta = bValue - aValue;
|
|
2289
|
+
if (aValue > 0) entry.ratio = bValue / aValue;
|
|
2290
|
+
}
|
|
2291
|
+
return entry;
|
|
2292
|
+
});
|
|
2293
|
+
}
|
|
2294
|
+
function diffGraphs(a, b) {
|
|
2295
|
+
const aActivities = new Map(a.activities.map((activity) => [activity.id, activity]));
|
|
2296
|
+
const bActivities = new Map(b.activities.map((activity) => [activity.id, activity]));
|
|
2297
|
+
const activityIds = unionIds(
|
|
2298
|
+
a.activities.map((activity) => activity.id),
|
|
2299
|
+
b.activities.map((activity) => activity.id)
|
|
2300
|
+
);
|
|
2301
|
+
const aTransitions = new Map(a.transitions.map((t) => [transitionKey2(t), t]));
|
|
2302
|
+
const bTransitions = new Map(b.transitions.map((t) => [transitionKey2(t), t]));
|
|
2303
|
+
const transitionIds = unionIds(
|
|
2304
|
+
a.transitions.map((t) => transitionKey2(t)),
|
|
2305
|
+
b.transitions.map((t) => transitionKey2(t))
|
|
2306
|
+
);
|
|
2307
|
+
return {
|
|
2308
|
+
activities: diffEntries(aActivities, bActivities, activityIds, (stats) => stats.instances),
|
|
2309
|
+
transitions: diffEntries(aTransitions, bTransitions, transitionIds, (stats) => stats.count),
|
|
2310
|
+
totals: { a: a.totals, b: b.totals }
|
|
2311
|
+
};
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
// src/core/replay-timeline.ts
|
|
2315
|
+
var REPLAY_TARGET_FRAMES = 300;
|
|
2316
|
+
var REPLAY_MAX_FRAMES = 5e3;
|
|
2317
|
+
function defaultReplayBucketMs(duration) {
|
|
2318
|
+
if (!Number.isFinite(duration) || duration <= 0) return 1;
|
|
2319
|
+
return Math.max(1, duration / REPLAY_TARGET_FRAMES);
|
|
2320
|
+
}
|
|
2321
|
+
function buildSegments(log, synchronizedStart) {
|
|
2322
|
+
const { cases } = asNormalizedLog(log);
|
|
2323
|
+
if (cases.length === 0) return { origin: 0, duration: 0, segments: [] };
|
|
2324
|
+
const origin = synchronizedStart ? 0 : Math.min(...cases.map((c) => c.start));
|
|
2325
|
+
const segments = [];
|
|
2326
|
+
let duration = 0;
|
|
2327
|
+
for (const trace of cases) {
|
|
2328
|
+
const base = synchronizedStart ? trace.start : origin;
|
|
2329
|
+
duration = Math.max(duration, trace.end - base);
|
|
2330
|
+
for (let i = 1; i < trace.events.length; i++) {
|
|
2331
|
+
const from = trace.events[i - 1];
|
|
2332
|
+
const to = trace.events[i];
|
|
2333
|
+
const enterAt = from.end - base;
|
|
2334
|
+
segments.push({
|
|
2335
|
+
caseId: trace.caseId,
|
|
2336
|
+
edgeId: `${from.activity}${EDGE_KEY_SEPARATOR}${to.activity}`,
|
|
2337
|
+
source: from.activity,
|
|
2338
|
+
target: to.activity,
|
|
2339
|
+
enterAt,
|
|
2340
|
+
exitAt: Math.max(enterAt, to.start - base)
|
|
2341
|
+
});
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
segments.sort((a, b) => a.enterAt - b.enterAt);
|
|
2345
|
+
return { origin, duration, segments };
|
|
2346
|
+
}
|
|
2347
|
+
function tokensAt(segments, t) {
|
|
2348
|
+
const tokens2 = [];
|
|
2349
|
+
for (const segment of segments) {
|
|
2350
|
+
if (segment.enterAt > t) break;
|
|
2351
|
+
if (t >= segment.exitAt) continue;
|
|
2352
|
+
tokens2.push({
|
|
2353
|
+
caseId: segment.caseId,
|
|
2354
|
+
edgeId: segment.edgeId,
|
|
2355
|
+
progress: (t - segment.enterAt) / (segment.exitAt - segment.enterAt)
|
|
2356
|
+
});
|
|
2357
|
+
}
|
|
2358
|
+
return tokens2;
|
|
2359
|
+
}
|
|
2360
|
+
function congestionIn(segments, start, end) {
|
|
2361
|
+
const casesByEdge = /* @__PURE__ */ new Map();
|
|
2362
|
+
for (const segment of segments) {
|
|
2363
|
+
if (segment.enterAt >= end) break;
|
|
2364
|
+
const overlaps = segment.exitAt > start || segment.enterAt >= start;
|
|
2365
|
+
if (!overlaps) continue;
|
|
2366
|
+
let cases = casesByEdge.get(segment.edgeId);
|
|
2367
|
+
if (!cases) casesByEdge.set(segment.edgeId, cases = /* @__PURE__ */ new Set());
|
|
2368
|
+
cases.add(segment.caseId);
|
|
2369
|
+
}
|
|
2370
|
+
const congestion = {};
|
|
2371
|
+
for (const [edgeId, cases] of casesByEdge) congestion[edgeId] = cases.size;
|
|
2372
|
+
return congestion;
|
|
2373
|
+
}
|
|
2374
|
+
function replayTimeline(log, options = {}) {
|
|
2375
|
+
const synchronizedStart = options.synchronizedStart ?? false;
|
|
2376
|
+
const { origin, duration, segments } = buildSegments(log, synchronizedStart);
|
|
2377
|
+
const requested = options.bucketMs !== void 0 && Number.isFinite(options.bucketMs) && options.bucketMs > 0 ? options.bucketMs : defaultReplayBucketMs(duration);
|
|
2378
|
+
const bucketMs = Math.max(requested, duration / (REPLAY_MAX_FRAMES - 1));
|
|
2379
|
+
const frameCount = Math.floor(duration / bucketMs) + 1;
|
|
2380
|
+
const frames = [];
|
|
2381
|
+
let peakCongestion = 0;
|
|
2382
|
+
for (let i = 0; i < frameCount; i++) {
|
|
2383
|
+
const t = i * bucketMs;
|
|
2384
|
+
const congestion = congestionIn(segments, t, t + bucketMs);
|
|
2385
|
+
for (const count of Object.values(congestion)) peakCongestion = Math.max(peakCongestion, count);
|
|
2386
|
+
frames.push({ t, tokens: tokensAt(segments, t), congestion });
|
|
2387
|
+
}
|
|
2388
|
+
return { origin, duration, bucketMs, synchronizedStart, segments, frames, peakCongestion };
|
|
2389
|
+
}
|
|
2390
|
+
function replayFrameAt(timeline, t) {
|
|
2391
|
+
const clamped = Math.min(Math.max(Number.isFinite(t) ? t : 0, 0), timeline.duration);
|
|
2392
|
+
const bucket = Math.min(Math.floor(clamped / timeline.bucketMs), timeline.frames.length - 1);
|
|
2393
|
+
return {
|
|
2394
|
+
t: clamped,
|
|
2395
|
+
tokens: tokensAt(timeline.segments, clamped),
|
|
2396
|
+
congestion: timeline.frames[Math.max(bucket, 0)]?.congestion ?? {}
|
|
2397
|
+
};
|
|
2398
|
+
}
|
|
2399
|
+
function rankReplayCongestion(timeline, limit = Number.POSITIVE_INFINITY) {
|
|
2400
|
+
const ends = /* @__PURE__ */ new Map();
|
|
2401
|
+
for (const s of timeline.segments) ends.set(s.edgeId, { source: s.source, target: s.target });
|
|
2402
|
+
const byEdge = /* @__PURE__ */ new Map();
|
|
2403
|
+
for (const frame of timeline.frames) {
|
|
2404
|
+
for (const [edgeId, count] of Object.entries(frame.congestion)) {
|
|
2405
|
+
let entry = byEdge.get(edgeId);
|
|
2406
|
+
if (!entry) {
|
|
2407
|
+
const { source, target } = ends.get(edgeId);
|
|
2408
|
+
byEdge.set(edgeId, entry = { edgeId, source, target, peak: 0, peakAt: 0, mean: 0 });
|
|
2409
|
+
}
|
|
2410
|
+
if (count > entry.peak) {
|
|
2411
|
+
entry.peak = count;
|
|
2412
|
+
entry.peakAt = frame.t;
|
|
2413
|
+
}
|
|
2414
|
+
entry.mean += count;
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
const frameCount = Math.max(timeline.frames.length, 1);
|
|
2418
|
+
return [...byEdge.values()].map((entry) => ({ ...entry, mean: entry.mean / frameCount })).sort(
|
|
2419
|
+
(a, b) => b.peak - a.peak || b.mean - a.mean || (a.edgeId < b.edgeId ? -1 : a.edgeId > b.edgeId ? 1 : 0)
|
|
2420
|
+
).slice(0, limit);
|
|
2421
|
+
}
|
|
2422
|
+
var REPLAY_TOKEN_RADIUS_RANGE = Object.freeze([3, 8]);
|
|
2423
|
+
function replayTokenRadius(congestion, peak) {
|
|
2424
|
+
const [min, max] = REPLAY_TOKEN_RADIUS_RANGE;
|
|
2425
|
+
if (!(peak > 0) || !(congestion > 0)) return min;
|
|
2426
|
+
const share = Math.min(congestion / peak, 1);
|
|
2427
|
+
return min + (max - min) * Math.sqrt(share);
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
// src/core/adapters/ocel.ts
|
|
2431
|
+
var OCEL_EVENT_ID_ATTRIBUTE = "__ocelEventId";
|
|
2432
|
+
var OCEL_OBJECT_REFS_ATTRIBUTE = "__objectRefs";
|
|
2433
|
+
function isRecord(value) {
|
|
2434
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2435
|
+
}
|
|
2436
|
+
function isScalar(value) {
|
|
2437
|
+
return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
2438
|
+
}
|
|
2439
|
+
function nonEmptyString(value) {
|
|
2440
|
+
return typeof value === "string" && value !== "";
|
|
2441
|
+
}
|
|
2442
|
+
function objectAttributes(object) {
|
|
2443
|
+
if (!Array.isArray(object.attributes)) return void 0;
|
|
2444
|
+
const latest = /* @__PURE__ */ new Map();
|
|
2445
|
+
for (const raw of object.attributes) {
|
|
2446
|
+
if (!isRecord(raw) || !nonEmptyString(raw.name) || !isScalar(raw.value)) continue;
|
|
2447
|
+
const parsed = typeof raw.time === "string" ? Date.parse(raw.time) : Number.NEGATIVE_INFINITY;
|
|
2448
|
+
const time = Number.isNaN(parsed) ? Number.NEGATIVE_INFINITY : parsed;
|
|
2449
|
+
const previous = latest.get(raw.name);
|
|
2450
|
+
if (previous === void 0 || time >= previous.time) {
|
|
2451
|
+
latest.set(raw.name, { time, value: raw.value });
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
if (latest.size === 0) return void 0;
|
|
2455
|
+
const out = {};
|
|
2456
|
+
for (const [name, entry] of latest) out[name] = entry.value;
|
|
2457
|
+
return out;
|
|
2458
|
+
}
|
|
2459
|
+
function fromOcel(input, options) {
|
|
2460
|
+
let document = input;
|
|
2461
|
+
if (typeof input === "string") {
|
|
2462
|
+
try {
|
|
2463
|
+
document = JSON.parse(input);
|
|
2464
|
+
} catch (error) {
|
|
2465
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2466
|
+
return { ok: false, errors: [{ type: "malformed_json", message }] };
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
if (!isRecord(document) || !Array.isArray(document.objectTypes) || !Array.isArray(document.objects) || !Array.isArray(document.events)) {
|
|
2470
|
+
return {
|
|
2471
|
+
ok: false,
|
|
2472
|
+
errors: [
|
|
2473
|
+
{
|
|
2474
|
+
type: "invalid_document",
|
|
2475
|
+
message: "expected an object with objectTypes, objects and events arrays"
|
|
2476
|
+
}
|
|
2477
|
+
]
|
|
2478
|
+
};
|
|
2479
|
+
}
|
|
2480
|
+
const errors = [];
|
|
2481
|
+
const declaredTypes = [];
|
|
2482
|
+
for (const declaration of document.objectTypes) {
|
|
2483
|
+
if (isRecord(declaration) && nonEmptyString(declaration.name)) {
|
|
2484
|
+
if (!declaredTypes.includes(declaration.name)) declaredTypes.push(declaration.name);
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
const projected = options?.objectTypes ?? declaredTypes;
|
|
2488
|
+
for (const type of projected) {
|
|
2489
|
+
if (!declaredTypes.includes(type)) {
|
|
2490
|
+
errors.push({
|
|
2491
|
+
type: "unknown_object_type",
|
|
2492
|
+
message: `object type "${type}" is not declared in objectTypes`
|
|
2493
|
+
});
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
const projectedSet = new Set(projected);
|
|
2497
|
+
const typeOfObject = /* @__PURE__ */ new Map();
|
|
2498
|
+
const attributesOfObject = /* @__PURE__ */ new Map();
|
|
2499
|
+
document.objects.forEach((object, objectIndex) => {
|
|
2500
|
+
if (!isRecord(object) || !nonEmptyString(object.id) || !nonEmptyString(object.type)) {
|
|
2501
|
+
errors.push({
|
|
2502
|
+
type: "invalid_document",
|
|
2503
|
+
message: "object is missing its id or type",
|
|
2504
|
+
objectIndex
|
|
2505
|
+
});
|
|
2506
|
+
return;
|
|
2507
|
+
}
|
|
2508
|
+
if (!declaredTypes.includes(object.type)) {
|
|
2509
|
+
errors.push({
|
|
2510
|
+
type: "unknown_object_type",
|
|
2511
|
+
message: `object "${object.id}" has undeclared type "${object.type}"`,
|
|
2512
|
+
objectIndex
|
|
2513
|
+
});
|
|
2514
|
+
return;
|
|
2515
|
+
}
|
|
2516
|
+
typeOfObject.set(object.id, object.type);
|
|
2517
|
+
const attributes = objectAttributes(object);
|
|
2518
|
+
if (attributes !== void 0) attributesOfObject.set(object.id, attributes);
|
|
2519
|
+
});
|
|
2520
|
+
const rowsByType = new Map(projected.map((type) => [type, []]));
|
|
2521
|
+
const activities = [];
|
|
2522
|
+
const seenActivities = /* @__PURE__ */ new Set();
|
|
2523
|
+
document.events.forEach((event, eventIndex) => {
|
|
2524
|
+
if (!isRecord(event)) {
|
|
2525
|
+
errors.push({ type: "invalid_document", message: "event is not an object", eventIndex });
|
|
2526
|
+
return;
|
|
2527
|
+
}
|
|
2528
|
+
if (!nonEmptyString(event.id)) {
|
|
2529
|
+
errors.push({ type: "missing_event_id", message: "event is missing its id", eventIndex });
|
|
2530
|
+
return;
|
|
2531
|
+
}
|
|
2532
|
+
if (!nonEmptyString(event.type)) {
|
|
2533
|
+
errors.push({
|
|
2534
|
+
type: "missing_event_type",
|
|
2535
|
+
message: `event "${event.id}" is missing its type`,
|
|
2536
|
+
eventIndex
|
|
2537
|
+
});
|
|
2538
|
+
return;
|
|
2539
|
+
}
|
|
2540
|
+
if (!nonEmptyString(event.time) || Number.isNaN(Date.parse(event.time))) {
|
|
2541
|
+
errors.push({
|
|
2542
|
+
type: "missing_timestamp",
|
|
2543
|
+
message: `event "${event.id}" has no parsable time`,
|
|
2544
|
+
eventIndex
|
|
2545
|
+
});
|
|
2546
|
+
return;
|
|
2547
|
+
}
|
|
2548
|
+
const refs = {};
|
|
2549
|
+
let broken = false;
|
|
2550
|
+
const relationships = Array.isArray(event.relationships) ? event.relationships : [];
|
|
2551
|
+
for (const relationship of relationships) {
|
|
2552
|
+
if (!isRecord(relationship) || !nonEmptyString(relationship.objectId)) continue;
|
|
2553
|
+
const type = typeOfObject.get(relationship.objectId);
|
|
2554
|
+
if (type === void 0) {
|
|
2555
|
+
errors.push({
|
|
2556
|
+
type: "unknown_object",
|
|
2557
|
+
message: `event "${event.id}" references undeclared object "${relationship.objectId}"`,
|
|
2558
|
+
eventIndex
|
|
2559
|
+
});
|
|
2560
|
+
broken = true;
|
|
2561
|
+
continue;
|
|
2562
|
+
}
|
|
2563
|
+
const ids = refs[type] ??= [];
|
|
2564
|
+
if (!ids.includes(relationship.objectId)) ids.push(relationship.objectId);
|
|
2565
|
+
}
|
|
2566
|
+
if (broken) return;
|
|
2567
|
+
if (!seenActivities.has(event.type)) {
|
|
2568
|
+
seenActivities.add(event.type);
|
|
2569
|
+
activities.push(event.type);
|
|
2570
|
+
}
|
|
2571
|
+
let attributes;
|
|
2572
|
+
if (Array.isArray(event.attributes)) {
|
|
2573
|
+
for (const attribute of event.attributes) {
|
|
2574
|
+
if (!isRecord(attribute) || !nonEmptyString(attribute.name)) continue;
|
|
2575
|
+
if (!isScalar(attribute.value)) continue;
|
|
2576
|
+
attributes ??= {};
|
|
2577
|
+
attributes[attribute.name] = attribute.value;
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
const encodedRefs = JSON.stringify(refs);
|
|
2581
|
+
for (const [type, objectIds] of Object.entries(refs)) {
|
|
2582
|
+
if (!projectedSet.has(type)) continue;
|
|
2583
|
+
const rows = rowsByType.get(type);
|
|
2584
|
+
for (const objectId of objectIds) {
|
|
2585
|
+
rows.push({
|
|
2586
|
+
caseId: objectId,
|
|
2587
|
+
activity: event.type,
|
|
2588
|
+
timestamp: event.time,
|
|
2589
|
+
attributes: {
|
|
2590
|
+
...attributes,
|
|
2591
|
+
[OCEL_EVENT_ID_ATTRIBUTE]: event.id,
|
|
2592
|
+
[OCEL_OBJECT_REFS_ATTRIBUTE]: encodedRefs
|
|
2593
|
+
}
|
|
2594
|
+
});
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
});
|
|
2598
|
+
if (errors.length > 0) return { ok: false, errors };
|
|
2599
|
+
const logs = {};
|
|
2600
|
+
for (const type of projected) {
|
|
2601
|
+
const events = rowsByType.get(type);
|
|
2602
|
+
const log = { events };
|
|
2603
|
+
let caseAttributes;
|
|
2604
|
+
for (const row of events) {
|
|
2605
|
+
const attributes = attributesOfObject.get(row.caseId);
|
|
2606
|
+
if (attributes === void 0 || caseAttributes?.[row.caseId] !== void 0) continue;
|
|
2607
|
+
caseAttributes ??= {};
|
|
2608
|
+
caseAttributes[row.caseId] = attributes;
|
|
2609
|
+
}
|
|
2610
|
+
if (caseAttributes !== void 0) log.caseAttributes = caseAttributes;
|
|
2611
|
+
logs[type] = log;
|
|
2612
|
+
}
|
|
2613
|
+
return { ok: true, logs, activities, objectTypes: [...projected] };
|
|
2614
|
+
}
|
|
2615
|
+
function readObjectRefs(row) {
|
|
2616
|
+
const raw = row.attributes?.[OCEL_OBJECT_REFS_ATTRIBUTE];
|
|
2617
|
+
if (typeof raw !== "string") return void 0;
|
|
2618
|
+
try {
|
|
2619
|
+
const parsed = JSON.parse(raw);
|
|
2620
|
+
if (!isRecord(parsed)) return void 0;
|
|
2621
|
+
const out = {};
|
|
2622
|
+
for (const [type, ids] of Object.entries(parsed)) {
|
|
2623
|
+
if (Array.isArray(ids)) out[type] = ids.filter((id) => typeof id === "string");
|
|
2624
|
+
}
|
|
2625
|
+
return out;
|
|
2626
|
+
} catch {
|
|
2627
|
+
return void 0;
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
// src/core/discover-object-centric-graph.ts
|
|
2632
|
+
function compareStrings3(a, b) {
|
|
2633
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
2634
|
+
}
|
|
2635
|
+
function countDistinctEvents(logs) {
|
|
2636
|
+
const ids = /* @__PURE__ */ new Map();
|
|
2637
|
+
let any = false;
|
|
2638
|
+
for (const log of logs) {
|
|
2639
|
+
for (const row of log.events) {
|
|
2640
|
+
const id = row.attributes?.[OCEL_EVENT_ID_ATTRIBUTE];
|
|
2641
|
+
if (typeof id !== "string") continue;
|
|
2642
|
+
any = true;
|
|
2643
|
+
let set = ids.get(row.activity);
|
|
2644
|
+
if (set === void 0) {
|
|
2645
|
+
set = /* @__PURE__ */ new Set();
|
|
2646
|
+
ids.set(row.activity, set);
|
|
2647
|
+
}
|
|
2648
|
+
set.add(id);
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
if (!any) return void 0;
|
|
2652
|
+
const out = /* @__PURE__ */ new Map();
|
|
2653
|
+
for (const [activity, set] of ids) out.set(activity, set.size);
|
|
2654
|
+
return out;
|
|
2655
|
+
}
|
|
2656
|
+
function mergeObjectCentricGraphs(graphsByType, objectTypes = Object.keys(graphsByType), eventCounts) {
|
|
2657
|
+
const merged = /* @__PURE__ */ new Map();
|
|
2658
|
+
const bestInstances = /* @__PURE__ */ new Map();
|
|
2659
|
+
const transitionsByType = {};
|
|
2660
|
+
const types = objectTypes.filter((type) => graphsByType[type] !== void 0);
|
|
2661
|
+
for (const type of types) {
|
|
2662
|
+
const graph = graphsByType[type];
|
|
2663
|
+
transitionsByType[type] = graph.transitions;
|
|
2664
|
+
for (const activity of graph.activities) {
|
|
2665
|
+
let entry = merged.get(activity.id);
|
|
2666
|
+
if (entry === void 0) {
|
|
2667
|
+
entry = {
|
|
2668
|
+
id: activity.id,
|
|
2669
|
+
label: activity.label,
|
|
2670
|
+
isStart: false,
|
|
2671
|
+
isEnd: false,
|
|
2672
|
+
duration: activity.duration,
|
|
2673
|
+
events: 0,
|
|
2674
|
+
perType: {}
|
|
2675
|
+
};
|
|
2676
|
+
merged.set(activity.id, entry);
|
|
2677
|
+
}
|
|
2678
|
+
entry.perType[type] = { instances: activity.instances, cases: activity.cases };
|
|
2679
|
+
entry.isStart ||= activity.isStart;
|
|
2680
|
+
entry.isEnd ||= activity.isEnd;
|
|
2681
|
+
if (activity.instances > (bestInstances.get(activity.id) ?? -1)) {
|
|
2682
|
+
bestInstances.set(activity.id, activity.instances);
|
|
2683
|
+
entry.duration = activity.duration;
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
const activities = [...merged.values()];
|
|
2688
|
+
for (const activity of activities) {
|
|
2689
|
+
activity.events = eventCounts?.get(activity.id) ?? bestInstances.get(activity.id) ?? 0;
|
|
2690
|
+
}
|
|
2691
|
+
activities.sort((a, b) => b.events - a.events || compareStrings3(a.id, b.id));
|
|
2692
|
+
const graphs = {};
|
|
2693
|
+
for (const type of types) graphs[type] = graphsByType[type];
|
|
2694
|
+
return { activities, transitionsByType, objectTypes: types, graphsByType: graphs };
|
|
2695
|
+
}
|
|
2696
|
+
function discoverObjectCentricGraph(logsByType, options = {}) {
|
|
2697
|
+
const { objectTypes = Object.keys(logsByType), ...discoverOptions } = options;
|
|
2698
|
+
const types = objectTypes.filter((type) => logsByType[type] !== void 0);
|
|
2699
|
+
const graphsByType = {};
|
|
2700
|
+
for (const type of types) {
|
|
2701
|
+
graphsByType[type] = discoverGraph(logsByType[type], discoverOptions);
|
|
2702
|
+
}
|
|
2703
|
+
const eventCounts = countDistinctEvents(types.map((type) => logsByType[type]));
|
|
2704
|
+
return mergeObjectCentricGraphs(graphsByType, types, eventCounts);
|
|
2705
|
+
}
|
|
2706
|
+
function abstractObjectCentricGraph(graph, perType = {}, fallback) {
|
|
2707
|
+
const graphsByType = {};
|
|
2708
|
+
const hiddenByType = {};
|
|
2709
|
+
for (const type of graph.objectTypes) {
|
|
2710
|
+
const source = graph.graphsByType[type];
|
|
2711
|
+
const options = perType[type] ?? fallback;
|
|
2712
|
+
if (options === void 0) {
|
|
2713
|
+
graphsByType[type] = source;
|
|
2714
|
+
hiddenByType[type] = { activities: 0, paths: 0 };
|
|
2715
|
+
continue;
|
|
2716
|
+
}
|
|
2717
|
+
const { hidden, ...abstracted } = abstractGraph(source, options);
|
|
2718
|
+
graphsByType[type] = abstracted;
|
|
2719
|
+
hiddenByType[type] = hidden;
|
|
2720
|
+
}
|
|
2721
|
+
const eventCounts = new Map(graph.activities.map((activity) => [activity.id, activity.events]));
|
|
2722
|
+
return {
|
|
2723
|
+
...mergeObjectCentricGraphs(graphsByType, graph.objectTypes, eventCounts),
|
|
2724
|
+
hiddenByType
|
|
2725
|
+
};
|
|
2726
|
+
}
|
|
2727
|
+
function objectCentricProcessGraph(graph) {
|
|
2728
|
+
const activities = graph.activities.map((activity) => {
|
|
2729
|
+
let cases = 0;
|
|
2730
|
+
for (const counts of Object.values(activity.perType)) cases += counts.cases;
|
|
2731
|
+
return {
|
|
2732
|
+
id: activity.id,
|
|
2733
|
+
label: activity.label,
|
|
2734
|
+
instances: activity.events,
|
|
2735
|
+
cases,
|
|
2736
|
+
isStart: activity.isStart,
|
|
2737
|
+
isEnd: activity.isEnd,
|
|
2738
|
+
duration: activity.duration
|
|
2739
|
+
};
|
|
2740
|
+
});
|
|
2741
|
+
const transitions = /* @__PURE__ */ new Map();
|
|
2742
|
+
const startActivities = {};
|
|
2743
|
+
const endActivities = {};
|
|
2744
|
+
const totals = { cases: 0, events: 0, variants: 0 };
|
|
2745
|
+
for (const activity of graph.activities) totals.events += activity.events;
|
|
2746
|
+
for (const type of graph.objectTypes) {
|
|
2747
|
+
const typeGraph = graph.graphsByType[type];
|
|
2748
|
+
totals.cases += typeGraph.totals.cases;
|
|
2749
|
+
totals.variants += typeGraph.totals.variants;
|
|
2750
|
+
for (const [id, count] of Object.entries(typeGraph.startActivities)) {
|
|
2751
|
+
startActivities[id] = (startActivities[id] ?? 0) + count;
|
|
2752
|
+
}
|
|
2753
|
+
for (const [id, count] of Object.entries(typeGraph.endActivities)) {
|
|
2754
|
+
endActivities[id] = (endActivities[id] ?? 0) + count;
|
|
2755
|
+
}
|
|
2756
|
+
for (const transition of graph.transitionsByType[type] ?? []) {
|
|
2757
|
+
const key = `${transition.source}${EDGE_KEY_SEPARATOR}${transition.target}`;
|
|
2758
|
+
const existing = transitions.get(key);
|
|
2759
|
+
if (existing === void 0) {
|
|
2760
|
+
transitions.set(key, { ...transition });
|
|
2761
|
+
continue;
|
|
2762
|
+
}
|
|
2763
|
+
if (transition.count > existing.count) existing.duration = transition.duration;
|
|
2764
|
+
existing.count += transition.count;
|
|
2765
|
+
existing.caseCount += transition.caseCount;
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
const transitionList = [...transitions.values()].sort(
|
|
2769
|
+
(a, b) => b.count - a.count || compareStrings3(a.source, b.source) || compareStrings3(a.target, b.target)
|
|
2770
|
+
);
|
|
2771
|
+
const sorted = (record) => {
|
|
2772
|
+
const out = {};
|
|
2773
|
+
for (const key of Object.keys(record).sort()) out[key] = record[key];
|
|
2774
|
+
return out;
|
|
2775
|
+
};
|
|
2776
|
+
return {
|
|
2777
|
+
activities,
|
|
2778
|
+
transitions: transitionList,
|
|
2779
|
+
startActivities: sorted(startActivities),
|
|
2780
|
+
endActivities: sorted(endActivities),
|
|
2781
|
+
totals
|
|
2782
|
+
};
|
|
2783
|
+
}
|
|
2784
|
+
function objectTypeColorScale(graph) {
|
|
2785
|
+
const zero = { min: 0, max: 0, mean: 0, median: 0, p90: 0, sum: 0, trimmedMean: 0 };
|
|
2786
|
+
const activities = graph.objectTypes.map((type) => {
|
|
2787
|
+
const typeGraph = graph.graphsByType[type];
|
|
2788
|
+
return {
|
|
2789
|
+
id: type,
|
|
2790
|
+
label: type,
|
|
2791
|
+
instances: typeGraph.totals.events,
|
|
2792
|
+
cases: typeGraph.totals.cases,
|
|
2793
|
+
isStart: false,
|
|
2794
|
+
isEnd: false,
|
|
2795
|
+
duration: zero
|
|
2796
|
+
};
|
|
2797
|
+
});
|
|
2798
|
+
return activityColorScale({
|
|
2799
|
+
activities,
|
|
2800
|
+
transitions: [],
|
|
2801
|
+
startActivities: {},
|
|
2802
|
+
endActivities: {},
|
|
2803
|
+
totals: { cases: 0, events: 0, variants: 0 }
|
|
2804
|
+
});
|
|
2805
|
+
}
|
|
1509
2806
|
export {
|
|
2807
|
+
ACTIVITY_COLOR_SLOTS,
|
|
2808
|
+
ACTIVITY_OTHER_TOKEN,
|
|
1510
2809
|
BPI_2012_ACTIVITIES,
|
|
1511
2810
|
BPI_2012_SUBSET_EPOCH,
|
|
1512
2811
|
DEFAULT_LIFECYCLE_VALUES,
|
|
2812
|
+
DEVIATION_TYPES,
|
|
1513
2813
|
DURATION_SAMPLE_CAP,
|
|
1514
2814
|
DURATION_UNIT_MS,
|
|
1515
2815
|
DurationSampler,
|
|
1516
2816
|
EDGE_KEY_SEPARATOR,
|
|
1517
2817
|
EMPTY_DURATION_STATS,
|
|
2818
|
+
OCEL_EVENT_ID_ATTRIBUTE,
|
|
2819
|
+
OCEL_OBJECT_REFS_ATTRIBUTE,
|
|
2820
|
+
REPLAY_MAX_FRAMES,
|
|
2821
|
+
REPLAY_TARGET_FRAMES,
|
|
2822
|
+
REPLAY_TOKEN_RADIUS_RANGE,
|
|
1518
2823
|
SYNTHETIC_ACTIVITIES,
|
|
1519
2824
|
SYNTHETIC_LOG_EPOCH,
|
|
1520
2825
|
TRIM_FRACTION,
|
|
1521
2826
|
VARIANT_KEY_SEPARATOR,
|
|
1522
2827
|
abstractGraph,
|
|
2828
|
+
abstractObjectCentricGraph,
|
|
2829
|
+
activityColorScale,
|
|
1523
2830
|
aggregatePerformance,
|
|
1524
2831
|
asNormalizedLog,
|
|
1525
2832
|
caseMatchesFilters,
|
|
2833
|
+
casesFromLog,
|
|
1526
2834
|
clampWidth,
|
|
2835
|
+
conformanceRateSeries,
|
|
1527
2836
|
createProcessWorker,
|
|
2837
|
+
defaultReplayBucketMs,
|
|
1528
2838
|
detectRework,
|
|
2839
|
+
diffGraphs,
|
|
1529
2840
|
discoverGraph,
|
|
2841
|
+
discoverObjectCentricGraph,
|
|
2842
|
+
durationQuartile,
|
|
2843
|
+
durationQuartileThresholds,
|
|
1530
2844
|
durationStats,
|
|
2845
|
+
emptyDeviationCounts,
|
|
1531
2846
|
emptyDurationStats,
|
|
1532
2847
|
extractVariants,
|
|
1533
2848
|
filterLog,
|
|
1534
2849
|
filterNormalizedLog,
|
|
1535
2850
|
fromCsv,
|
|
1536
2851
|
fromFlatRows,
|
|
2852
|
+
fromOcel,
|
|
2853
|
+
fromXes,
|
|
1537
2854
|
generateBpi2012Subset,
|
|
1538
2855
|
generateSyntheticLog,
|
|
1539
2856
|
handleProcessRequest,
|
|
1540
2857
|
isNormalizedLog,
|
|
2858
|
+
liftHappyPath,
|
|
2859
|
+
mergeObjectCentricGraphs,
|
|
1541
2860
|
minMax,
|
|
1542
2861
|
normalizeLifecycle,
|
|
1543
2862
|
normalizeLog,
|
|
2863
|
+
objectCentricProcessGraph,
|
|
2864
|
+
objectTypeColorScale,
|
|
1544
2865
|
parseDelimited,
|
|
1545
2866
|
performanceValue,
|
|
1546
2867
|
quantile,
|
|
1547
2868
|
quantileSorted,
|
|
2869
|
+
quartileOf,
|
|
2870
|
+
rankReplayCongestion,
|
|
2871
|
+
readObjectRefs,
|
|
1548
2872
|
reconcileGraph,
|
|
2873
|
+
replayActivities,
|
|
2874
|
+
replayFrameAt,
|
|
2875
|
+
replayTimeline,
|
|
2876
|
+
replayTokenRadius,
|
|
2877
|
+
replayTrace,
|
|
2878
|
+
segmentKey,
|
|
2879
|
+
segmentOrderByFrequency,
|
|
2880
|
+
segmentOrderForVariant,
|
|
2881
|
+
segmentsFor,
|
|
1549
2882
|
toEpochMs,
|
|
2883
|
+
tokenReplay,
|
|
1550
2884
|
variantId,
|
|
1551
2885
|
variantKey
|
|
1552
2886
|
};
|