@elabs-ai/components-process 4.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.
Files changed (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +73 -0
  3. package/dist/core/index.d.ts +1029 -0
  4. package/dist/core/index.js +1553 -0
  5. package/dist/core/index.js.map +1 -0
  6. package/dist/core/process-worker.js +462 -0
  7. package/dist/core/process-worker.js.map +1 -0
  8. package/dist/index.d.ts +1153 -0
  9. package/dist/index.js +3146 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/test/index.d.ts +196 -0
  12. package/dist/test/index.js +527 -0
  13. package/dist/test/index.js.map +1 -0
  14. package/package.json +80 -0
  15. package/src/abstraction-controls/abstraction-controls-fixtures.ts +86 -0
  16. package/src/abstraction-controls/abstraction-controls.stories.tsx +188 -0
  17. package/src/abstraction-controls/abstraction-controls.test.tsx +226 -0
  18. package/src/abstraction-controls/abstraction-controls.tsx +288 -0
  19. package/src/abstraction-controls/auto-abstraction.test.ts +196 -0
  20. package/src/abstraction-controls/auto-abstraction.ts +128 -0
  21. package/src/abstraction-controls/index.ts +4 -0
  22. package/src/core/abstract-graph.test.ts +209 -0
  23. package/src/core/abstract-graph.ts +407 -0
  24. package/src/core/adapters/csv.test.ts +131 -0
  25. package/src/core/adapters/csv.ts +146 -0
  26. package/src/core/adapters/flat.test.ts +149 -0
  27. package/src/core/adapters/flat.ts +168 -0
  28. package/src/core/aggregate-performance.test.ts +208 -0
  29. package/src/core/aggregate-performance.ts +200 -0
  30. package/src/core/detect-rework.test.ts +134 -0
  31. package/src/core/detect-rework.ts +100 -0
  32. package/src/core/discover-graph.test.ts +378 -0
  33. package/src/core/discover-graph.ts +202 -0
  34. package/src/core/duration-stats.test.ts +116 -0
  35. package/src/core/duration-stats.ts +162 -0
  36. package/src/core/event-log.test.ts +224 -0
  37. package/src/core/event-log.ts +244 -0
  38. package/src/core/extract-variants.test.ts +126 -0
  39. package/src/core/extract-variants.ts +140 -0
  40. package/src/core/filter-log.test.ts +193 -0
  41. package/src/core/filter-log.ts +215 -0
  42. package/src/core/fixtures/generate-bpi-2012-subset.test.ts +50 -0
  43. package/src/core/fixtures/generate-bpi-2012-subset.ts +216 -0
  44. package/src/core/fixtures/generate-bpi-2012-subset.write.ts +40 -0
  45. package/src/core/fixtures/order-to-cash-small.json +200 -0
  46. package/src/core/fixtures/synthetic-log.test.ts +109 -0
  47. package/src/core/fixtures/synthetic-log.ts +167 -0
  48. package/src/core/index.ts +118 -0
  49. package/src/core/reconcile-graph.test.ts +175 -0
  50. package/src/core/reconcile-graph.ts +107 -0
  51. package/src/core/scale.test.ts +80 -0
  52. package/src/core/scale.ts +100 -0
  53. package/src/core/types.ts +151 -0
  54. package/src/core/worker/create-process-worker.test.ts +255 -0
  55. package/src/core/worker/create-process-worker.ts +211 -0
  56. package/src/core/worker/process-worker.ts +80 -0
  57. package/src/index.ts +29 -0
  58. package/src/metric-layer-switch/index.ts +6 -0
  59. package/src/metric-layer-switch/metric-layer-switch.stories.tsx +131 -0
  60. package/src/metric-layer-switch/metric-layer-switch.test.tsx +102 -0
  61. package/src/metric-layer-switch/metric-layer-switch.tsx +276 -0
  62. package/src/process-explorer.stories.tsx +392 -0
  63. package/src/process-kpi-strip/index.ts +6 -0
  64. package/src/process-kpi-strip/process-kpi-strip.stories.tsx +128 -0
  65. package/src/process-kpi-strip/process-kpi-strip.test.tsx +106 -0
  66. package/src/process-kpi-strip/process-kpi-strip.tsx +237 -0
  67. package/src/process-map/index.ts +13 -0
  68. package/src/process-map/map-model.test.ts +326 -0
  69. package/src/process-map/map-model.ts +873 -0
  70. package/src/process-map/process-activity-node.tsx +200 -0
  71. package/src/process-map/process-map-context.ts +71 -0
  72. package/src/process-map/process-map.stories.tsx +673 -0
  73. package/src/process-map/process-map.test.tsx +523 -0
  74. package/src/process-map/process-map.tsx +979 -0
  75. package/src/process-map/process-transition-edge.test.tsx +160 -0
  76. package/src/process-map/process-transition-edge.tsx +151 -0
  77. package/src/process-map/use-process-layout.test.tsx +265 -0
  78. package/src/process-map/use-process-layout.ts +315 -0
  79. package/src/test/contract.test.ts +99 -0
  80. package/src/test/contract.ts +118 -0
  81. package/src/test/doubles.test.tsx +51 -0
  82. package/src/test/doubles.tsx +82 -0
  83. package/src/test/index.ts +34 -0
  84. package/src/test/primitives.tsx +35 -0
  85. package/src/use-process-explorer/index.ts +8 -0
  86. package/src/use-process-explorer/use-process-explorer.test.ts +564 -0
  87. package/src/use-process-explorer/use-process-explorer.ts +540 -0
@@ -0,0 +1,527 @@
1
+ "use client";
2
+
3
+ // src/test/contract.ts
4
+ var ProcessContractError = class extends Error {
5
+ constructor(componentName, message) {
6
+ super(`${componentName}: ${message}`);
7
+ this.name = "ProcessContractError";
8
+ }
9
+ };
10
+ function isProcessGraph(value) {
11
+ return !!value && typeof value === "object" && Array.isArray(value.activities) && Array.isArray(value.transitions);
12
+ }
13
+ function isVariantArray(value) {
14
+ return Array.isArray(value) && value.every((entry) => typeof entry === "object" && entry !== null && "sequence" in entry);
15
+ }
16
+ function assertProcessContract(componentName, props, spec) {
17
+ const data = props[spec.dataProp];
18
+ if (spec.dataProp === "graph" && !isProcessGraph(data)) {
19
+ throw new ProcessContractError(
20
+ componentName,
21
+ `"graph" prop must be a ProcessGraph, got ${typeof data}`
22
+ );
23
+ }
24
+ if (spec.dataProp === "variants" && !isVariantArray(data)) {
25
+ throw new ProcessContractError(
26
+ componentName,
27
+ `"variants" prop must be a Variant[], got ${typeof data}`
28
+ );
29
+ }
30
+ for (const key of spec.requiredProps ?? []) {
31
+ if (props[key] === void 0) {
32
+ throw new ProcessContractError(componentName, `missing required prop "${key}"`);
33
+ }
34
+ }
35
+ }
36
+ function buildProcessDoublePayload(componentName, props, spec) {
37
+ const data = props[spec.dataProp];
38
+ const dataLength = spec.dataProp === "graph" && isProcessGraph(data) ? data.activities.length : Array.isArray(data) ? data.length : 0;
39
+ const payload = { component: componentName, dataLength };
40
+ if ("selection" in props) payload.selection = props.selection;
41
+ return payload;
42
+ }
43
+ function readProcessDoubleProps(el) {
44
+ const raw = el.getAttribute("data-process-props");
45
+ return raw ? JSON.parse(raw) : null;
46
+ }
47
+
48
+ // src/test/doubles.tsx
49
+ import { forwardRef } from "react";
50
+ import { jsx } from "react/jsx-runtime";
51
+ var PROCESS_MAP_SPEC = { dataProp: "graph" };
52
+ var VARIANT_EXPLORER_SPEC = { dataProp: "variants" };
53
+ var PROCESS_KPI_STRIP_SPEC = { dataProp: "graph" };
54
+ function createProcessDouble(name, spec) {
55
+ const Double = forwardRef(function ProcessTestDouble(props, ref) {
56
+ const record = props;
57
+ assertProcessContract(name, record, spec);
58
+ const payload = buildProcessDoublePayload(name, record, spec);
59
+ return /* @__PURE__ */ jsx(
60
+ "div",
61
+ {
62
+ ref,
63
+ "data-slot": "process-test-double",
64
+ "data-process-double": name,
65
+ "data-process-props": JSON.stringify(payload),
66
+ className: props.className,
67
+ style: props.style
68
+ }
69
+ );
70
+ });
71
+ Double.displayName = name;
72
+ return Double;
73
+ }
74
+ var ProcessMapDouble = createProcessDouble(
75
+ "ProcessMapDouble",
76
+ PROCESS_MAP_SPEC
77
+ );
78
+ var VariantExplorerDouble = createProcessDouble(
79
+ "VariantExplorerDouble",
80
+ VARIANT_EXPLORER_SPEC
81
+ );
82
+ var ProcessKpiStripDouble = createProcessDouble(
83
+ "ProcessKpiStripDouble",
84
+ PROCESS_KPI_STRIP_SPEC
85
+ );
86
+
87
+ // src/core/scale.ts
88
+ function ascending(a, b) {
89
+ return a - b;
90
+ }
91
+ function quantileSorted(sorted, q) {
92
+ const n = sorted.length;
93
+ if (n === 0) return 0;
94
+ if (n === 1) return sorted[0];
95
+ const clamped = q < 0 ? 0 : q > 1 ? 1 : q;
96
+ const pos = clamped * (n - 1);
97
+ const lo = Math.floor(pos);
98
+ const hi = Math.ceil(pos);
99
+ const loValue = sorted[lo];
100
+ if (lo === hi) return loValue;
101
+ const hiValue = sorted[hi];
102
+ return loValue + (hiValue - loValue) * (pos - lo);
103
+ }
104
+
105
+ // src/core/duration-stats.ts
106
+ var TRIM_FRACTION = 0.1;
107
+ var EMPTY_DURATION_STATS = Object.freeze({
108
+ min: 0,
109
+ max: 0,
110
+ mean: 0,
111
+ median: 0,
112
+ p90: 0,
113
+ sum: 0,
114
+ trimmedMean: 0
115
+ });
116
+ function emptyDurationStats() {
117
+ return { ...EMPTY_DURATION_STATS };
118
+ }
119
+ function durationStats(samples) {
120
+ const sorted = [];
121
+ for (const s of samples) if (Number.isFinite(s)) sorted.push(s);
122
+ const n = sorted.length;
123
+ if (n === 0) return emptyDurationStats();
124
+ sorted.sort(ascending);
125
+ let sum = 0;
126
+ for (const s of sorted) sum += s;
127
+ const trim = Math.floor(n * TRIM_FRACTION);
128
+ const lo = trim;
129
+ const hi = n - trim;
130
+ let trimmedMean;
131
+ if (hi - lo <= 0) {
132
+ trimmedMean = sum / n;
133
+ } else {
134
+ let trimmedSum = 0;
135
+ for (let i = lo; i < hi; i += 1) trimmedSum += sorted[i];
136
+ trimmedMean = trimmedSum / (hi - lo);
137
+ }
138
+ return {
139
+ min: sorted[0],
140
+ max: sorted[n - 1],
141
+ mean: sum / n,
142
+ median: quantileSorted(sorted, 0.5),
143
+ p90: quantileSorted(sorted, 0.9),
144
+ sum,
145
+ trimmedMean
146
+ };
147
+ }
148
+ var DURATION_SAMPLE_CAP = 4096;
149
+ function mulberry32(seed) {
150
+ let a = seed >>> 0;
151
+ return () => {
152
+ a = a + 1831565813 >>> 0;
153
+ let t = a;
154
+ t = Math.imul(t ^ t >>> 15, t | 1);
155
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
156
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
157
+ };
158
+ }
159
+ var DurationSampler = class {
160
+ capacity;
161
+ random;
162
+ reservoir = [];
163
+ /** Number of finite samples offered, including those the reservoir discarded. */
164
+ seen = 0;
165
+ total = 0;
166
+ lowest = Number.POSITIVE_INFINITY;
167
+ highest = Number.NEGATIVE_INFINITY;
168
+ constructor(seed = 2654435769, capacity = DURATION_SAMPLE_CAP) {
169
+ this.capacity = capacity > 0 ? capacity : 1;
170
+ this.random = mulberry32(seed);
171
+ }
172
+ /** Offer one sample. Non-finite values are ignored. */
173
+ add(sample) {
174
+ if (!Number.isFinite(sample)) return;
175
+ this.total += sample;
176
+ if (sample < this.lowest) this.lowest = sample;
177
+ if (sample > this.highest) this.highest = sample;
178
+ if (this.reservoir.length < this.capacity) {
179
+ this.reservoir.push(sample);
180
+ } else {
181
+ const j = Math.floor(this.random() * (this.seen + 1));
182
+ if (j < this.capacity) this.reservoir[j] = sample;
183
+ }
184
+ this.seen += 1;
185
+ }
186
+ /** True number of finite samples offered. */
187
+ get size() {
188
+ return this.seen;
189
+ }
190
+ /**
191
+ * Summarize what was collected. `sum`, `mean`, `min` and `max` are EXACT even past the
192
+ * cap (they are accumulated, not read off the reservoir); the remaining three are
193
+ * computed from the retained sample.
194
+ */
195
+ stats() {
196
+ if (this.seen === 0) return emptyDurationStats();
197
+ const fromReservoir = durationStats(this.reservoir);
198
+ return {
199
+ ...fromReservoir,
200
+ min: this.lowest,
201
+ max: this.highest,
202
+ sum: this.total,
203
+ mean: this.total / this.seen
204
+ };
205
+ }
206
+ };
207
+
208
+ // src/core/event-log.ts
209
+ function isNormalizedLog(log) {
210
+ return Array.isArray(log.cases);
211
+ }
212
+ function asNormalizedLog(log) {
213
+ return isNormalizedLog(log) ? log : normalizeLog(log);
214
+ }
215
+ function toEpochMs(value) {
216
+ if (value === void 0 || value === null) return Number.NaN;
217
+ if (typeof value === "number") return Number.isFinite(value) ? value : Number.NaN;
218
+ if (value instanceof Date) return value.getTime();
219
+ const parsed = Date.parse(value);
220
+ if (!Number.isNaN(parsed)) return parsed;
221
+ const asNumber = Number(value);
222
+ return Number.isFinite(asNumber) && value.trim() !== "" ? asNumber : Number.NaN;
223
+ }
224
+ function byTimestamp(a, b) {
225
+ if (Number.isNaN(a.at) || Number.isNaN(b.at)) return 0;
226
+ return a.at - b.at;
227
+ }
228
+ function normalizeLog(log) {
229
+ const staged = /* @__PURE__ */ new Map();
230
+ const order = [];
231
+ for (const row of log.events) {
232
+ if (!row || typeof row.caseId !== "string" || row.caseId === "") continue;
233
+ if (typeof row.activity !== "string" || row.activity === "") continue;
234
+ let bucket = staged.get(row.caseId);
235
+ if (bucket === void 0) {
236
+ bucket = [];
237
+ staged.set(row.caseId, bucket);
238
+ order.push(row.caseId);
239
+ }
240
+ bucket.push({ at: toEpochMs(row.timestamp), row });
241
+ }
242
+ const cases = [];
243
+ let events = 0;
244
+ for (const caseId of order) {
245
+ const bucket = staged.get(caseId);
246
+ bucket.sort(byTimestamp);
247
+ const instances = [];
248
+ const open = /* @__PURE__ */ new Map();
249
+ for (const { at, row } of bucket) {
250
+ if (row.lifecycle === "start") {
251
+ const index = instances.length;
252
+ instances.push(makeInstance(row, at, at, true));
253
+ const queue = open.get(row.activity);
254
+ if (queue === void 0) open.set(row.activity, [index]);
255
+ else queue.push(index);
256
+ continue;
257
+ }
258
+ if (row.lifecycle === "complete") {
259
+ const queue = open.get(row.activity);
260
+ const index = queue?.shift();
261
+ if (index !== void 0) {
262
+ const instance = instances[index];
263
+ instance.end = at;
264
+ instance.duration = durationOf(instance.start, at);
265
+ instance.isOpen = false;
266
+ if (instance.resource === void 0 && row.resource !== void 0) {
267
+ instance.resource = row.resource;
268
+ }
269
+ if (instance.attributes === void 0 && row.attributes !== void 0) {
270
+ instance.attributes = row.attributes;
271
+ }
272
+ continue;
273
+ }
274
+ }
275
+ const end = at;
276
+ const explicitStart = toEpochMs(row.startTimestamp);
277
+ const start = Number.isNaN(explicitStart) ? end : explicitStart;
278
+ instances.push(makeInstance(row, start, end, false));
279
+ }
280
+ if (instances.length === 0) continue;
281
+ instances.sort(byStart);
282
+ let caseStart = Number.POSITIVE_INFINITY;
283
+ let caseEnd = Number.NEGATIVE_INFINITY;
284
+ for (const instance of instances) {
285
+ if (Number.isFinite(instance.start) && instance.start < caseStart) caseStart = instance.start;
286
+ if (Number.isFinite(instance.end) && instance.end > caseEnd) caseEnd = instance.end;
287
+ }
288
+ const hasExtent = caseStart !== Number.POSITIVE_INFINITY && caseEnd !== Number.NEGATIVE_INFINITY;
289
+ const normalizedCase = {
290
+ caseId,
291
+ events: instances,
292
+ start: hasExtent ? caseStart : Number.NaN,
293
+ end: hasExtent ? caseEnd : Number.NaN,
294
+ duration: hasExtent ? durationOf(caseStart, caseEnd) : Number.NaN
295
+ };
296
+ const caseAttributes = log.caseAttributes?.[caseId];
297
+ if (caseAttributes !== void 0) normalizedCase.attributes = caseAttributes;
298
+ cases.push(normalizedCase);
299
+ events += instances.length;
300
+ }
301
+ return { cases, totals: { cases: cases.length, events } };
302
+ }
303
+ function byStart(a, b) {
304
+ if (Number.isNaN(a.start) || Number.isNaN(b.start)) return 0;
305
+ return a.start - b.start;
306
+ }
307
+ function durationOf(start, end) {
308
+ const delta = end - start;
309
+ if (!Number.isFinite(delta)) return Number.NaN;
310
+ return delta < 0 ? 0 : delta;
311
+ }
312
+ function makeInstance(row, start, end, isOpen) {
313
+ const instance = {
314
+ activity: row.activity,
315
+ start,
316
+ end,
317
+ duration: durationOf(start, end),
318
+ isOpen
319
+ };
320
+ if (row.resource !== void 0) instance.resource = row.resource;
321
+ if (row.attributes !== void 0) instance.attributes = row.attributes;
322
+ return instance;
323
+ }
324
+
325
+ // src/core/extract-variants.ts
326
+ var VARIANT_KEY_SEPARATOR = "";
327
+ function variantKey(sequence) {
328
+ return sequence.join(VARIANT_KEY_SEPARATOR);
329
+ }
330
+ function fnv1a32(text, basis) {
331
+ let hash = basis >>> 0;
332
+ for (let i = 0; i < text.length; i += 1) {
333
+ hash ^= text.charCodeAt(i);
334
+ hash = Math.imul(hash, 16777619) >>> 0;
335
+ }
336
+ return hash >>> 0;
337
+ }
338
+ function hex8(value) {
339
+ return (value >>> 0).toString(16).padStart(8, "0");
340
+ }
341
+ function variantId(sequence) {
342
+ const key = variantKey(sequence);
343
+ return `v${hex8(fnv1a32(key, 2166136261))}${hex8(fnv1a32(key, 2216829733))}`;
344
+ }
345
+ function extractVariants(log) {
346
+ const normalized = asNormalizedLog(log);
347
+ const totalCases = normalized.cases.length;
348
+ if (totalCases === 0) return [];
349
+ const groups = /* @__PURE__ */ new Map();
350
+ for (const kase of normalized.cases) {
351
+ const sequence = sequenceOf(kase);
352
+ const key = variantKey(sequence);
353
+ let group = groups.get(key);
354
+ if (group === void 0) {
355
+ group = { key, sequence, caseIds: [], durations: [] };
356
+ groups.set(key, group);
357
+ }
358
+ group.caseIds.push(kase.caseId);
359
+ group.durations.push(kase.duration);
360
+ }
361
+ const ranked = [...groups.values()].sort(
362
+ (a, b) => b.caseIds.length - a.caseIds.length || (a.key < b.key ? -1 : a.key > b.key ? 1 : 0)
363
+ );
364
+ const taken = /* @__PURE__ */ new Set();
365
+ const variants = [];
366
+ let cumulativeCount = 0;
367
+ for (const group of ranked) {
368
+ const count = group.caseIds.length;
369
+ cumulativeCount += count;
370
+ let id = variantId(group.sequence);
371
+ if (taken.has(id)) {
372
+ let suffix = 1;
373
+ while (taken.has(`${id}-${suffix}`)) suffix += 1;
374
+ id = `${id}-${suffix}`;
375
+ }
376
+ taken.add(id);
377
+ variants.push({
378
+ id,
379
+ sequence: group.sequence,
380
+ count,
381
+ share: count / totalCases,
382
+ cumulativeShare: cumulativeCount / totalCases,
383
+ caseIds: group.caseIds,
384
+ duration: durationStats(group.durations)
385
+ });
386
+ }
387
+ return variants;
388
+ }
389
+ function sequenceOf(kase) {
390
+ const sequence = new Array(kase.events.length);
391
+ for (let i = 0; i < kase.events.length; i += 1) {
392
+ sequence[i] = kase.events[i].activity;
393
+ }
394
+ return sequence;
395
+ }
396
+
397
+ // src/core/discover-graph.ts
398
+ var EDGE_KEY_SEPARATOR = VARIANT_KEY_SEPARATOR;
399
+ function discoverGraph(log, options = {}) {
400
+ const flowTime = options.flowTime ?? "idle_time";
401
+ const capacity = options.maxDurationSamples ?? DURATION_SAMPLE_CAP;
402
+ const normalized = asNormalizedLog(log);
403
+ const activities = /* @__PURE__ */ new Map();
404
+ const transitions = /* @__PURE__ */ new Map();
405
+ const startActivities = /* @__PURE__ */ new Map();
406
+ const endActivities = /* @__PURE__ */ new Map();
407
+ const variantKeys = /* @__PURE__ */ new Set();
408
+ let samplerIndex = 0;
409
+ const nextSeed = () => {
410
+ samplerIndex += 1;
411
+ return (2654435769 ^ Math.imul(samplerIndex, 2654435761)) >>> 0;
412
+ };
413
+ let events = 0;
414
+ const seenActivities = /* @__PURE__ */ new Set();
415
+ const seenEdges = /* @__PURE__ */ new Set();
416
+ for (const kase of normalized.cases) {
417
+ const trace = kase.events;
418
+ if (trace.length === 0) continue;
419
+ events += trace.length;
420
+ seenActivities.clear();
421
+ seenEdges.clear();
422
+ const sequence = new Array(trace.length);
423
+ for (let i = 0; i < trace.length; i += 1) {
424
+ const event = trace[i];
425
+ const name = event.activity;
426
+ sequence[i] = name;
427
+ let activity = activities.get(name);
428
+ if (activity === void 0) {
429
+ activity = { instances: 0, cases: 0, duration: new DurationSampler(nextSeed(), capacity) };
430
+ activities.set(name, activity);
431
+ }
432
+ activity.instances += 1;
433
+ activity.duration.add(event.duration);
434
+ if (!seenActivities.has(name)) {
435
+ seenActivities.add(name);
436
+ activity.cases += 1;
437
+ }
438
+ if (i === 0) continue;
439
+ const previous = trace[i - 1];
440
+ const source = previous.activity;
441
+ const key = `${source}${EDGE_KEY_SEPARATOR}${name}`;
442
+ let edge = transitions.get(key);
443
+ if (edge === void 0) {
444
+ edge = {
445
+ source,
446
+ target: name,
447
+ count: 0,
448
+ caseCount: 0,
449
+ duration: new DurationSampler(nextSeed(), capacity)
450
+ };
451
+ transitions.set(key, edge);
452
+ }
453
+ edge.count += 1;
454
+ edge.duration.add(
455
+ flowTime === "inter_start_time" ? event.start - previous.start : event.start - previous.end
456
+ );
457
+ if (!seenEdges.has(key)) {
458
+ seenEdges.add(key);
459
+ edge.caseCount += 1;
460
+ }
461
+ }
462
+ const first = trace[0];
463
+ const last = trace[trace.length - 1];
464
+ startActivities.set(first.activity, (startActivities.get(first.activity) ?? 0) + 1);
465
+ endActivities.set(last.activity, (endActivities.get(last.activity) ?? 0) + 1);
466
+ variantKeys.add(sequence.join(VARIANT_KEY_SEPARATOR));
467
+ }
468
+ const activityList = [];
469
+ for (const [id, accumulator] of activities) {
470
+ activityList.push({
471
+ id,
472
+ label: id,
473
+ instances: accumulator.instances,
474
+ cases: accumulator.cases,
475
+ isStart: startActivities.has(id),
476
+ isEnd: endActivities.has(id),
477
+ duration: accumulator.duration.stats()
478
+ });
479
+ }
480
+ activityList.sort((a, b) => b.instances - a.instances || compareStrings(a.id, b.id));
481
+ const transitionList = [];
482
+ for (const accumulator of transitions.values()) {
483
+ transitionList.push({
484
+ source: accumulator.source,
485
+ target: accumulator.target,
486
+ count: accumulator.count,
487
+ caseCount: accumulator.caseCount,
488
+ duration: accumulator.duration.stats(),
489
+ isSelfLoop: accumulator.source === accumulator.target,
490
+ isBackEdge: false
491
+ });
492
+ }
493
+ transitionList.sort(
494
+ (a, b) => b.count - a.count || compareStrings(a.source, b.source) || compareStrings(a.target, b.target)
495
+ );
496
+ return {
497
+ activities: activityList,
498
+ transitions: transitionList,
499
+ startActivities: toSortedRecord(startActivities),
500
+ endActivities: toSortedRecord(endActivities),
501
+ totals: { cases: normalized.cases.length, events, variants: variantKeys.size }
502
+ };
503
+ }
504
+ function compareStrings(a, b) {
505
+ return a < b ? -1 : a > b ? 1 : 0;
506
+ }
507
+ function toSortedRecord(counts) {
508
+ const out = {};
509
+ for (const key of [...counts.keys()].sort()) out[key] = counts.get(key);
510
+ return out;
511
+ }
512
+
513
+ // src/test/primitives.tsx
514
+ function withProcessFixture(log) {
515
+ return { log, graph: discoverGraph(log), variants: extractVariants(log) };
516
+ }
517
+ export {
518
+ ProcessContractError,
519
+ ProcessKpiStripDouble,
520
+ ProcessMapDouble,
521
+ VariantExplorerDouble,
522
+ assertProcessContract,
523
+ buildProcessDoublePayload,
524
+ readProcessDoubleProps,
525
+ withProcessFixture
526
+ };
527
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/test/contract.ts","../../src/test/doubles.tsx","../../src/core/scale.ts","../../src/core/duration-stats.ts","../../src/core/event-log.ts","../../src/core/extract-variants.ts","../../src/core/discover-graph.ts","../../src/test/primitives.tsx"],"sourcesContent":["/**\n * The double contract engine — RM-053.\n *\n * Mirrors `@elabs-ai/components-charts/src/test/contract.ts` (issue #364): a real component\n * whose rendering depends on a heavy engine (here, the `flow`-composed process views) gets a\n * `vi.mock`-swappable test double that (a) validates the same required-prop contract the real\n * component would enforce, so a broken test fails loudly instead of silently rendering\n * nothing, (b) never imports the engine, and (c) exposes what it was given as an inspectable\n * `data-process-props` attribute so a test can assert on props without a real graph/variant\n * layout ever running under jsdom.\n *\n * `ProcessMap`, `VariantExplorer` and `ProcessKpiStrip` do not exist yet in this package's\n * public barrel — RM-051/052/054 land them (`src/index.ts` still ships no components on\n * purpose). The doubles built on this contract are therefore named with an explicit `Double`\n * suffix rather than the real component name: there is no real same-named export yet for\n * `vi.mock` to swap in for. A follow-up item that lands the real components should rename the\n * doubles to match (dropping the suffix) so a consumer can\n * `vi.mock(\"@elabs-ai/components-process\", () => import(\"@elabs-ai/components-process/test\"))`\n * the way `@elabs-ai/components-charts` consumers do — that rename is out of this item's scope.\n */\nimport type { ProcessGraph, Variant } from \"../core/types\";\n\n/** Selection carried by a process view's coordinated-selection contract (RM-068 completes it). */\nexport type ProcessSelection = null | { kind: \"node\"; id: string } | { kind: \"edge\"; id: string };\n\n/** What {@link assertProcessContract} checks for one double. */\nexport interface ProcessContractSpec {\n /** Name of the prop carrying the double's primary data payload. */\n dataProp: \"graph\" | \"variants\";\n /** Other props the real component requires; the double must not silently accept `undefined`. */\n requiredProps?: string[];\n}\n\n/** Thrown by {@link assertProcessContract} when a double is used with an invalid prop shape. */\nexport class ProcessContractError extends Error {\n constructor(componentName: string, message: string) {\n super(`${componentName}: ${message}`);\n this.name = \"ProcessContractError\";\n }\n}\n\nfunction isProcessGraph(value: unknown): value is ProcessGraph {\n return (\n !!value &&\n typeof value === \"object\" &&\n Array.isArray((value as ProcessGraph).activities) &&\n Array.isArray((value as ProcessGraph).transitions)\n );\n}\n\nfunction isVariantArray(value: unknown): value is Variant[] {\n return (\n Array.isArray(value) &&\n value.every((entry) => typeof entry === \"object\" && entry !== null && \"sequence\" in entry)\n );\n}\n\n/**\n * Validate a double's props against its contract spec. Throws {@link ProcessContractError} on\n * a missing/invalid required prop — mirroring what the real component would fail on at\n * runtime, so a test that gets the props wrong fails loudly rather than silently rendering an\n * empty double.\n */\nexport function assertProcessContract(\n componentName: string,\n props: Record<string, unknown>,\n spec: ProcessContractSpec,\n): void {\n const data = props[spec.dataProp];\n if (spec.dataProp === \"graph\" && !isProcessGraph(data)) {\n throw new ProcessContractError(\n componentName,\n `\"graph\" prop must be a ProcessGraph, got ${typeof data}`,\n );\n }\n if (spec.dataProp === \"variants\" && !isVariantArray(data)) {\n throw new ProcessContractError(\n componentName,\n `\"variants\" prop must be a Variant[], got ${typeof data}`,\n );\n }\n for (const key of spec.requiredProps ?? []) {\n if (props[key] === undefined) {\n throw new ProcessContractError(componentName, `missing required prop \"${key}\"`);\n }\n }\n}\n\n/** What a double records to `data-process-props`, for assertions without a real layout. */\nexport interface ProcessDoublePayload {\n component: string;\n dataLength: number;\n selection?: ProcessSelection;\n}\n\n/** Build the inspectable payload a double serializes into `data-process-props`. */\nexport function buildProcessDoublePayload(\n componentName: string,\n props: Record<string, unknown>,\n spec: ProcessContractSpec,\n): ProcessDoublePayload {\n const data = props[spec.dataProp];\n const dataLength =\n spec.dataProp === \"graph\" && isProcessGraph(data)\n ? data.activities.length\n : Array.isArray(data)\n ? data.length\n : 0;\n const payload: ProcessDoublePayload = { component: componentName, dataLength };\n if (\"selection\" in props) payload.selection = props.selection as ProcessSelection;\n return payload;\n}\n\n/** Read a mounted double's payload back out of the DOM (companion to {@link buildProcessDoublePayload}). */\nexport function readProcessDoubleProps(el: Element): ProcessDoublePayload | null {\n const raw = el.getAttribute(\"data-process-props\");\n return raw ? (JSON.parse(raw) as ProcessDoublePayload) : null;\n}\n","\"use client\";\n\n/**\n * Test doubles for the process package's not-yet-shipped view components — RM-053.\n *\n * Mirrors `@elabs-ai/components-charts/src/test/doubles.tsx`'s factory shape: each double is a\n * plain `forwardRef<HTMLDivElement, P>` that validates its contract\n * ({@link assertProcessContract}) then renders an inert `<div>` carrying its props as a\n * `data-process-props` JSON attribute — cheap enough to mount by the thousand in a test, with\n * nothing that needs a real graph/variant layout to run under jsdom.\n */\nimport { forwardRef } from \"react\";\nimport type { HTMLAttributes } from \"react\";\n\nimport type { ProcessGraph, Variant } from \"../core/types\";\nimport {\n assertProcessContract,\n buildProcessDoublePayload,\n type ProcessContractSpec,\n type ProcessSelection,\n} from \"./contract\";\n\ninterface DoubleOwnProps extends HTMLAttributes<HTMLDivElement> {\n selection?: ProcessSelection;\n onSelectionChange?: (selection: ProcessSelection) => void;\n}\n\ninterface ProcessMapDoubleProps extends DoubleOwnProps {\n graph: ProcessGraph;\n}\n\ninterface VariantExplorerDoubleProps extends DoubleOwnProps {\n variants: Variant[];\n}\n\ninterface ProcessKpiStripDoubleProps extends HTMLAttributes<HTMLDivElement> {\n graph: ProcessGraph;\n}\n\nconst PROCESS_MAP_SPEC: ProcessContractSpec = { dataProp: \"graph\" };\nconst VARIANT_EXPLORER_SPEC: ProcessContractSpec = { dataProp: \"variants\" };\nconst PROCESS_KPI_STRIP_SPEC: ProcessContractSpec = { dataProp: \"graph\" };\n\nfunction createProcessDouble<P extends DoubleOwnProps>(name: string, spec: ProcessContractSpec) {\n const Double = forwardRef<HTMLDivElement, P>(function ProcessTestDouble(props, ref) {\n const record = props as unknown as Record<string, unknown>;\n assertProcessContract(name, record, spec);\n const payload = buildProcessDoublePayload(name, record, spec);\n return (\n <div\n ref={ref}\n data-slot=\"process-test-double\"\n data-process-double={name}\n data-process-props={JSON.stringify(payload)}\n className={props.className}\n style={props.style}\n />\n );\n });\n Double.displayName = name;\n return Double;\n}\n\n/** Stand-in for the future `ProcessMap` (RM-051). Named with a `Double` suffix — see contract.ts header. */\nexport const ProcessMapDouble = createProcessDouble<ProcessMapDoubleProps>(\n \"ProcessMapDouble\",\n PROCESS_MAP_SPEC,\n);\n\n/** Stand-in for the future `VariantExplorer` (RM-052). */\nexport const VariantExplorerDouble = createProcessDouble<VariantExplorerDoubleProps>(\n \"VariantExplorerDouble\",\n VARIANT_EXPLORER_SPEC,\n);\n\n/** Stand-in for the future `ProcessKpiStrip` (RM-054). */\nexport const ProcessKpiStripDouble = createProcessDouble<ProcessKpiStripDoubleProps>(\n \"ProcessKpiStripDouble\",\n PROCESS_KPI_STRIP_SPEC,\n);\n\nexport type { ProcessMapDoubleProps, VariantExplorerDoubleProps, ProcessKpiStripDoubleProps };\n","/**\n * Visual-scale helpers shared by every process view — RM-049.\n *\n * One implementation, so RM-051's edge width, RM-054's coverage bar and RM-050's\n * abstraction threshold all agree on what \"the 90th percentile\" and \"map this count to a\n * stroke width\" mean. Re-deriving a quantile per component is how two views end up\n * disagreeing about the same log.\n *\n * All three are pure and total: they never throw, and they answer for the degenerate\n * inputs (empty array, zero-width domain) rather than returning `NaN`.\n */\n\n/** Ascending numeric comparator. Extracted so every sort in `/core` uses the same one. */\nexport function ascending(a: number, b: number): number {\n return a - b;\n}\n\n/** Only finite samples take part in a scale; `NaN`/`Infinity` are dropped, not propagated. */\nfunction finiteOnly(values: readonly number[]): number[] {\n const out: number[] = [];\n for (const v of values) if (Number.isFinite(v)) out.push(v);\n return out;\n}\n\n/**\n * The extent of `values`, ignoring non-finite entries.\n *\n * @returns `[min, max]`, or `[0, 0]` when there is nothing finite to measure.\n */\nexport function minMax(values: readonly number[]): [number, number] {\n let min = Number.POSITIVE_INFINITY;\n let max = Number.NEGATIVE_INFINITY;\n let seen = false;\n for (const v of values) {\n if (!Number.isFinite(v)) continue;\n seen = true;\n if (v < min) min = v;\n if (v > max) max = v;\n }\n return seen ? [min, max] : [0, 0];\n}\n\n/**\n * The `q`-quantile of an ALREADY ASCENDING-SORTED, all-finite array, by linear\n * interpolation between order statistics (the R-7 / `d3.quantile` definition).\n *\n * Exported for callers that already hold a sorted array — {@link durationStats} sorts\n * once and reads several quantiles off it. Use {@link quantile} when the input is not\n * known to be sorted.\n */\nexport function quantileSorted(sorted: readonly number[], q: number): number {\n const n = sorted.length;\n if (n === 0) return 0;\n if (n === 1) return sorted[0] as number;\n const clamped = q < 0 ? 0 : q > 1 ? 1 : q;\n const pos = clamped * (n - 1);\n const lo = Math.floor(pos);\n const hi = Math.ceil(pos);\n const loValue = sorted[lo] as number;\n if (lo === hi) return loValue;\n const hiValue = sorted[hi] as number;\n return loValue + (hiValue - loValue) * (pos - lo);\n}\n\n/**\n * The `q`-quantile of `values` (0 ≤ `q` ≤ 1; out-of-range values are clamped).\n *\n * Sorts a COPY, so the caller's array is untouched. Non-finite entries are dropped;\n * an empty (or all-non-finite) input answers `0`.\n */\nexport function quantile(values: readonly number[], q: number): number {\n return quantileSorted(finiteOnly(values).sort(ascending), q);\n}\n\n/**\n * Map `value` from `domain` onto `range`, clamped to the range's endpoints.\n *\n * The workhorse behind \"frequency → stroke width\" and \"share → bar length\". A\n * DEGENERATE domain (`d0 === d1`, e.g. a graph whose every edge has the same count)\n * answers `range[0]`: with a zero-width domain every input sits at the domain floor, so\n * the range floor is the consistent answer — and it is what the clamped interpolation\n * gives for any `value <= d0`. A caller that wants a different neutral (the widest\n * stroke, say) special-cases it explicitly rather than relying on a surprise here.\n *\n * A descending domain (`d0 > d1`) is honoured — it simply inverts the mapping.\n */\nexport function clampWidth(\n value: number,\n domain: readonly [number, number],\n range: readonly [number, number],\n): number {\n const [d0, d1] = domain;\n const [r0, r1] = range;\n if (!Number.isFinite(value)) return r0;\n const span = d1 - d0;\n if (span === 0) return r0;\n const t = (value - d0) / span;\n const clamped = t < 0 ? 0 : t > 1 ? 1 : t;\n return r0 + (r1 - r0) * clamped;\n}\n","/**\n * Duration summary statistics — RM-049.\n *\n * The single implementation every part of the core reads: per-activity execution time,\n * per-edge flow time and per-variant case time all pass through here, which is what makes\n * \"the variant's median agrees with the edge's median on the same fixture\" true by\n * construction rather than by coincidence.\n *\n * Algorithm shape (which statistics a discovered graph carries per activity and per edge)\n * follows pm4js — see `ATTRIBUTION.md`. No pm4js code is copied; the arithmetic below is\n * the ordinary textbook definition.\n */\nimport { ascending, quantileSorted } from \"./scale\";\nimport type { DurationStats } from \"./types\";\n\n/** The fraction discarded from EACH tail by `trimmedMean`. */\nexport const TRIM_FRACTION = 0.1;\n\n/** All-zero statistics — the answer for an empty sample set. */\nexport const EMPTY_DURATION_STATS: Readonly<DurationStats> = Object.freeze({\n min: 0,\n max: 0,\n mean: 0,\n median: 0,\n p90: 0,\n sum: 0,\n trimmedMean: 0,\n});\n\n/** A fresh mutable copy of {@link EMPTY_DURATION_STATS}. */\nexport function emptyDurationStats(): DurationStats {\n return { ...EMPTY_DURATION_STATS };\n}\n\n/**\n * Summarize `samples` (milliseconds) as {@link DurationStats}.\n *\n * - Non-finite samples are DROPPED, not propagated — one unparseable timestamp upstream\n * must not turn an entire activity's statistics into `NaN`.\n * - The input array is not mutated (a copy is sorted).\n * - `p90` uses linear interpolation between order statistics (R-7 / `d3.quantile`).\n * - `trimmedMean` discards the lowest and highest {@link TRIM_FRACTION} of samples. When\n * trimming would leave nothing (fewer than 5 samples, where `floor(n * 0.1)` is 0 or\n * the tails meet), it degrades to the plain mean rather than to `NaN`.\n * - An empty (or all-non-finite) input answers all zeros.\n */\nexport function durationStats(samples: readonly number[]): DurationStats {\n const sorted: number[] = [];\n for (const s of samples) if (Number.isFinite(s)) sorted.push(s);\n const n = sorted.length;\n if (n === 0) return emptyDurationStats();\n sorted.sort(ascending);\n\n let sum = 0;\n for (const s of sorted) sum += s;\n\n const trim = Math.floor(n * TRIM_FRACTION);\n const lo = trim;\n const hi = n - trim;\n let trimmedMean: number;\n if (hi - lo <= 0) {\n trimmedMean = sum / n;\n } else {\n let trimmedSum = 0;\n for (let i = lo; i < hi; i += 1) trimmedSum += sorted[i] as number;\n trimmedMean = trimmedSum / (hi - lo);\n }\n\n return {\n min: sorted[0] as number,\n max: sorted[n - 1] as number,\n mean: sum / n,\n median: quantileSorted(sorted, 0.5),\n p90: quantileSorted(sorted, 0.9),\n sum,\n trimmedMean,\n };\n}\n\n/** Default reservoir capacity for a single activity's or edge's duration samples. */\nexport const DURATION_SAMPLE_CAP = 4096;\n\n/**\n * A deterministic 32-bit PRNG (mulberry32). Same seed, same stream — which is the whole\n * point: the reservoir below must make identical keep/discard decisions on every run, in\n * every engine, or two runs over one log would produce different `p90`s.\n */\nfunction mulberry32(seed: number): () => number {\n let a = seed >>> 0;\n return () => {\n a = (a + 0x6d2b79f5) >>> 0;\n let t = a;\n t = Math.imul(t ^ (t >>> 15), t | 1);\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\n/**\n * Bounded duration-sample collector.\n *\n * A log with a million events would otherwise hold a million numbers per hot edge just to\n * compute seven statistics from them. The first {@link DURATION_SAMPLE_CAP} samples are\n * kept outright; past the cap the collector switches to reservoir sampling (Algorithm R),\n * so the retained set stays a uniform sample of everything seen while memory stays flat.\n *\n * `count` keeps the TRUE number of samples offered, and `sum` the true total, so a\n * capped edge still reports an exact `sum` and an exact `mean`; only the order statistics\n * (`median`, `p90`, `min`, `max`, `trimmedMean`) are estimated from the reservoir.\n */\nexport class DurationSampler {\n private readonly capacity: number;\n private readonly random: () => number;\n private readonly reservoir: number[] = [];\n /** Number of finite samples offered, including those the reservoir discarded. */\n private seen = 0;\n private total = 0;\n private lowest = Number.POSITIVE_INFINITY;\n private highest = Number.NEGATIVE_INFINITY;\n\n constructor(seed = 0x9e3779b9, capacity: number = DURATION_SAMPLE_CAP) {\n this.capacity = capacity > 0 ? capacity : 1;\n this.random = mulberry32(seed);\n }\n\n /** Offer one sample. Non-finite values are ignored. */\n add(sample: number): void {\n if (!Number.isFinite(sample)) return;\n this.total += sample;\n if (sample < this.lowest) this.lowest = sample;\n if (sample > this.highest) this.highest = sample;\n if (this.reservoir.length < this.capacity) {\n this.reservoir.push(sample);\n } else {\n const j = Math.floor(this.random() * (this.seen + 1));\n if (j < this.capacity) this.reservoir[j] = sample;\n }\n this.seen += 1;\n }\n\n /** True number of finite samples offered. */\n get size(): number {\n return this.seen;\n }\n\n /**\n * Summarize what was collected. `sum`, `mean`, `min` and `max` are EXACT even past the\n * cap (they are accumulated, not read off the reservoir); the remaining three are\n * computed from the retained sample.\n */\n stats(): DurationStats {\n if (this.seen === 0) return emptyDurationStats();\n const fromReservoir = durationStats(this.reservoir);\n return {\n ...fromReservoir,\n min: this.lowest,\n max: this.highest,\n sum: this.total,\n mean: this.total / this.seen,\n };\n }\n}\n","/**\n * Event-log normalization — RM-049.\n *\n * Turns a flat `EventRow[]` into per-case traces of INSTANCES: one entry per real\n * activity execution, carrying a resolved `start` and `end` in epoch milliseconds. Every\n * other module in `/core` consumes the normalized form, so the messy parts of a raw log\n * (three timestamp encodings, lifecycle rows split across two lines, rows arriving out of\n * order) are handled exactly once, here.\n *\n * Deterministic: same rows in, same traces out. No `Date.now()`, no randomness.\n */\nimport type { EventLog, EventRow } from \"./types\";\n\n/** One activity execution, with both ends resolved to epoch milliseconds. */\nexport interface NormalizedEvent {\n activity: string;\n /**\n * Start of the execution. Equals `end` for an atomic event — an event with no\n * `lifecycle` pair and no `startTimestamp` has no observed duration, and inventing one\n * would put fabricated numbers into every downstream statistic.\n */\n start: number;\n /** Completion of the execution. */\n end: number;\n /** `end - start`, never negative (a clock-skewed pair is floored at 0). */\n duration: number;\n resource?: string;\n attributes?: Record<string, string | number | boolean | null>;\n /**\n * True when this instance came from a `lifecycle: \"start\"` row that never got a\n * matching `\"complete\"`. Its `end` is provisional (it equals `start`), so a performance\n * view can choose to exclude it.\n */\n isOpen: boolean;\n}\n\n/** One case (process instance): its trace, plus the case's own extent. */\nexport interface NormalizedCase {\n caseId: string;\n /** Instances in ascending `start` order. Never empty. */\n events: NormalizedEvent[];\n /** Earliest `start` in the trace. */\n start: number;\n /** Latest `end` in the trace. */\n end: number;\n /** `end - start` — the case's throughput time. */\n duration: number;\n /** Per-case attributes carried over from `EventLog.caseAttributes`, when present. */\n attributes?: Record<string, unknown>;\n}\n\n/** A normalized log: cases in first-appearance order, plus totals. */\nexport interface NormalizedLog {\n cases: NormalizedCase[];\n totals: { cases: number; events: number };\n}\n\n/** A log in either shape. Every derivation entry point accepts both. */\nexport type AnyLog = EventLog | NormalizedLog;\n\n/** Narrowing predicate — `true` when `log` has already been through {@link normalizeLog}. */\nexport function isNormalizedLog(log: AnyLog): log is NormalizedLog {\n return Array.isArray((log as NormalizedLog).cases);\n}\n\n/** Normalize `log` unless it already is normalized. The idempotent entry point. */\nexport function asNormalizedLog(log: AnyLog): NormalizedLog {\n return isNormalizedLog(log) ? log : normalizeLog(log);\n}\n\n/**\n * Resolve the three accepted timestamp encodings to epoch milliseconds.\n *\n * Returns `NaN` for an unparseable value rather than throwing: one bad cell in a\n * ten-thousand-row CSV must not abort the whole import. Downstream, a `NaN` sorts as\n * equal (so the row keeps its input position) and is dropped from duration samples.\n */\nexport function toEpochMs(value: string | number | Date | undefined | null): number {\n if (value === undefined || value === null) return Number.NaN;\n if (typeof value === \"number\") return Number.isFinite(value) ? value : Number.NaN;\n if (value instanceof Date) return value.getTime();\n const parsed = Date.parse(value);\n if (!Number.isNaN(parsed)) return parsed;\n // A bare numeric string (\"1735725600000\") is a legitimate epoch encoding that\n // `Date.parse` reads as a year in some engines; treat it as epoch ms explicitly.\n const asNumber = Number(value);\n return Number.isFinite(asNumber) && value.trim() !== \"\" ? asNumber : Number.NaN;\n}\n\n/** Stable comparator that leaves `NaN`-timestamped rows in their input order. */\nfunction byTimestamp(a: { at: number }, b: { at: number }): number {\n if (Number.isNaN(a.at) || Number.isNaN(b.at)) return 0;\n return a.at - b.at;\n}\n\ninterface StagedRow {\n at: number;\n row: EventRow;\n}\n\n/**\n * Group rows by case, order each case in time, and pair `lifecycle` rows into instances.\n *\n * The pairing rule, stated precisely because five downstream items depend on it:\n *\n * - Rows are grouped by `caseId` in ONE pass and each case is sorted independently. The\n * whole log is never sorted — sorting 200 000 rows to then split them is the shape that\n * makes a browser import feel broken.\n * - A `lifecycle: \"start\"` row OPENS an instance at its position in the case's timeline.\n * - A `lifecycle: \"complete\"` row CLOSES the oldest still-open instance of the SAME\n * activity, wherever that start sits in the trace. The two rows therefore do not have\n * to be adjacent: interleaved `A-start, B-start, A-complete, B-complete` pairs\n * correctly, and so does a log whose rows arrive in the wrong order entirely, because\n * the per-case sort runs first.\n * - Repeated executions of one activity in a case pair oldest-start-to-earliest-complete\n * (FIFO), which is the only pairing that keeps intervals non-overlapping for a\n * sequential resource.\n * - A `\"complete\"` with no open start becomes an instance in its own right, using an\n * explicit `startTimestamp` when the row carries one.\n * - A `\"start\"` that is never completed stays OPEN: `end === start` and `isOpen` is true.\n * - A row with no `lifecycle` is atomic: `start` comes from `startTimestamp` when given,\n * otherwise from `timestamp` itself.\n *\n * Rows with an empty `caseId` or `activity` are dropped — they cannot take part in a\n * directly-follows relation, and keeping them would put an unnamed node in every graph.\n */\nexport function normalizeLog(log: EventLog): NormalizedLog {\n const staged = new Map<string, StagedRow[]>();\n const order: string[] = [];\n\n for (const row of log.events) {\n if (!row || typeof row.caseId !== \"string\" || row.caseId === \"\") continue;\n if (typeof row.activity !== \"string\" || row.activity === \"\") continue;\n let bucket = staged.get(row.caseId);\n if (bucket === undefined) {\n bucket = [];\n staged.set(row.caseId, bucket);\n order.push(row.caseId);\n }\n bucket.push({ at: toEpochMs(row.timestamp), row });\n }\n\n const cases: NormalizedCase[] = [];\n let events = 0;\n\n for (const caseId of order) {\n const bucket = staged.get(caseId) as StagedRow[];\n // `Array.prototype.sort` is stable (ES2019), so equal timestamps keep input order.\n bucket.sort(byTimestamp);\n\n const instances: NormalizedEvent[] = [];\n /** activity → FIFO queue of indices into `instances` awaiting a `\"complete\"`. */\n const open = new Map<string, number[]>();\n\n for (const { at, row } of bucket) {\n if (row.lifecycle === \"start\") {\n const index = instances.length;\n instances.push(makeInstance(row, at, at, true));\n const queue = open.get(row.activity);\n if (queue === undefined) open.set(row.activity, [index]);\n else queue.push(index);\n continue;\n }\n\n if (row.lifecycle === \"complete\") {\n const queue = open.get(row.activity);\n const index = queue?.shift();\n if (index !== undefined) {\n const instance = instances[index] as NormalizedEvent;\n instance.end = at;\n instance.duration = durationOf(instance.start, at);\n instance.isOpen = false;\n if (instance.resource === undefined && row.resource !== undefined) {\n instance.resource = row.resource;\n }\n if (instance.attributes === undefined && row.attributes !== undefined) {\n instance.attributes = row.attributes;\n }\n continue;\n }\n }\n\n const end = at;\n const explicitStart = toEpochMs(row.startTimestamp);\n const start = Number.isNaN(explicitStart) ? end : explicitStart;\n instances.push(makeInstance(row, start, end, false));\n }\n\n if (instances.length === 0) continue;\n\n // Pairing can move an instance's start earlier than the row that created it, so the\n // trace is re-ordered by resolved start. It is already almost sorted, which is the\n // best case for the engine's TimSort — this is not a second full sort in practice.\n instances.sort(byStart);\n\n let caseStart = Number.POSITIVE_INFINITY;\n let caseEnd = Number.NEGATIVE_INFINITY;\n for (const instance of instances) {\n if (Number.isFinite(instance.start) && instance.start < caseStart) caseStart = instance.start;\n if (Number.isFinite(instance.end) && instance.end > caseEnd) caseEnd = instance.end;\n }\n const hasExtent =\n caseStart !== Number.POSITIVE_INFINITY && caseEnd !== Number.NEGATIVE_INFINITY;\n\n const normalizedCase: NormalizedCase = {\n caseId,\n events: instances,\n start: hasExtent ? caseStart : Number.NaN,\n end: hasExtent ? caseEnd : Number.NaN,\n duration: hasExtent ? durationOf(caseStart, caseEnd) : Number.NaN,\n };\n const caseAttributes = log.caseAttributes?.[caseId];\n if (caseAttributes !== undefined) normalizedCase.attributes = caseAttributes;\n\n cases.push(normalizedCase);\n events += instances.length;\n }\n\n return { cases, totals: { cases: cases.length, events } };\n}\n\nfunction byStart(a: NormalizedEvent, b: NormalizedEvent): number {\n if (Number.isNaN(a.start) || Number.isNaN(b.start)) return 0;\n return a.start - b.start;\n}\n\nfunction durationOf(start: number, end: number): number {\n const delta = end - start;\n if (!Number.isFinite(delta)) return Number.NaN;\n return delta < 0 ? 0 : delta;\n}\n\nfunction makeInstance(row: EventRow, start: number, end: number, isOpen: boolean): NormalizedEvent {\n const instance: NormalizedEvent = {\n activity: row.activity,\n start,\n end,\n duration: durationOf(start, end),\n isOpen,\n };\n if (row.resource !== undefined) instance.resource = row.resource;\n if (row.attributes !== undefined) instance.attributes = row.attributes;\n return instance;\n}\n","/**\n * Variant extraction — RM-049.\n *\n * A \"variant\" is one distinct activity sequence, together with every case that follows\n * it. Grouping traces by their sequence and ranking those groups by frequency is the\n * standard second view onto a log (the first being the directly-follows graph), and the\n * grouping shape below follows pm4js; see `ATTRIBUTION.md`. No pm4js code is copied.\n *\n * Deterministic: ids are hashes of the sequence, ordering is fully tie-broken, and the\n * cumulative share is computed from counts rather than by summing floats, so it is exactly\n * `1` on the last variant instead of `0.9999999999999998`.\n */\nimport { durationStats } from \"./duration-stats\";\nimport { asNormalizedLog, type AnyLog, type NormalizedCase } from \"./event-log\";\nimport type { Variant } from \"./types\";\n\n/**\n * Separator joining a sequence into its variant key.\n *\n * U+0001 (START OF HEADING) is a C0 control character. No real activity name contains\n * one, which is what makes joining on it lossless where a printable separator\n * (`\" -> \"`, `\",\"`) would collide with a name that happens to contain the separator.\n */\nexport const VARIANT_KEY_SEPARATOR = \"\\u0001\";\n\n/** Join an activity sequence into its canonical variant key. */\nexport function variantKey(sequence: readonly string[]): string {\n return sequence.join(VARIANT_KEY_SEPARATOR);\n}\n\n/** FNV-1a, 32-bit. Small, fast, dependency-free and identical in every engine. */\nfunction fnv1a32(text: string, basis: number): number {\n let hash = basis >>> 0;\n for (let i = 0; i < text.length; i += 1) {\n hash ^= text.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193) >>> 0;\n }\n return hash >>> 0;\n}\n\nfunction hex8(value: number): string {\n return (value >>> 0).toString(16).padStart(8, \"0\");\n}\n\n/**\n * The stable id for an activity sequence: `v` followed by 64 bits of FNV-1a over the\n * variant key, as hex.\n *\n * Exported because a selection is a wave-wide concern — RM-052's variant explorer and\n * RM-053's case table both need to name the same variant without holding the object, and\n * a hash of the sequence is the only identifier that survives a re-run, a worker boundary\n * and a URL.\n */\nexport function variantId(sequence: readonly string[]): string {\n const key = variantKey(sequence);\n return `v${hex8(fnv1a32(key, 0x811c9dc5))}${hex8(fnv1a32(key, 0x84222325))}`;\n}\n\ninterface VariantAccumulator {\n key: string;\n sequence: string[];\n caseIds: string[];\n durations: number[];\n}\n\n/**\n * Group `log`'s cases into variants, ranked by frequency.\n *\n * Ordering is `count` descending, ties broken by the variant key ascending — a total\n * order, so the same log always produces the same array in the same positions.\n *\n * `share` is `count / cases`. `cumulativeShare` is the running `cumulativeCount / cases`,\n * which is monotonically non-decreasing by construction and exactly `1` on the last\n * entry. `duration` summarizes the END-TO-END case durations of the variant's cases, so\n * it answers \"how long does this path take\", not \"how long does one step take\".\n *\n * An empty log yields an empty array — never a single zero-count variant.\n */\nexport function extractVariants(log: AnyLog): Variant[] {\n const normalized = asNormalizedLog(log);\n const totalCases = normalized.cases.length;\n if (totalCases === 0) return [];\n\n const groups = new Map<string, VariantAccumulator>();\n for (const kase of normalized.cases) {\n const sequence = sequenceOf(kase);\n const key = variantKey(sequence);\n let group = groups.get(key);\n if (group === undefined) {\n group = { key, sequence, caseIds: [], durations: [] };\n groups.set(key, group);\n }\n group.caseIds.push(kase.caseId);\n group.durations.push(kase.duration);\n }\n\n const ranked = [...groups.values()].sort(\n (a, b) => b.caseIds.length - a.caseIds.length || (a.key < b.key ? -1 : a.key > b.key ? 1 : 0),\n );\n\n const taken = new Set<string>();\n const variants: Variant[] = [];\n let cumulativeCount = 0;\n\n for (const group of ranked) {\n const count = group.caseIds.length;\n cumulativeCount += count;\n // A 64-bit hash collision between two different sequences is vanishingly unlikely,\n // but \"vanishingly unlikely\" is not \"impossible\" and a duplicate id would silently\n // merge two rows in a table. Disambiguate deterministically: `ranked` is totally\n // ordered, so the suffix an id receives is the same on every run.\n let id = variantId(group.sequence);\n if (taken.has(id)) {\n let suffix = 1;\n while (taken.has(`${id}-${suffix}`)) suffix += 1;\n id = `${id}-${suffix}`;\n }\n taken.add(id);\n\n variants.push({\n id,\n sequence: group.sequence,\n count,\n share: count / totalCases,\n cumulativeShare: cumulativeCount / totalCases,\n caseIds: group.caseIds,\n duration: durationStats(group.durations),\n });\n }\n\n return variants;\n}\n\nfunction sequenceOf(kase: NormalizedCase): string[] {\n const sequence = new Array<string>(kase.events.length);\n for (let i = 0; i < kase.events.length; i += 1) {\n sequence[i] = (kase.events[i] as { activity: string }).activity;\n }\n return sequence;\n}\n","/**\n * Directly-follows graph discovery — RM-049.\n *\n * The directly-follows abstraction (one node per activity, one edge per adjacent pair in\n * a trace, with frequency and performance aggregates on both) is the standard starting\n * point of every process-mining view. The shape of what a discovered graph carries —\n * per-activity instance and case counts, per-edge count and case count, start/end\n * activity tallies — follows pm4js; see `ATTRIBUTION.md`. No pm4js code is copied, and\n * nothing here derives from the AGPL Python reference implementation.\n *\n * Deterministic and layout-free: `isBackEdge` is always `false` here, because whether an\n * edge points backwards is a property of a LAYOUT, not of a log. A layout pass sets it.\n */\nimport { DURATION_SAMPLE_CAP, DurationSampler } from \"./duration-stats\";\nimport { asNormalizedLog, type AnyLog, type NormalizedEvent } from \"./event-log\";\nimport { VARIANT_KEY_SEPARATOR } from \"./extract-variants\";\nimport type { ActivityStats, FlowTime, ProcessGraph, TransitionStats } from \"./types\";\n\n/**\n * Separator used to key an edge. The same C0 control character the variant key uses, and\n * for the same reason: no activity name can contain it, so `source + SEP + target` is an\n * injective key.\n */\nexport const EDGE_KEY_SEPARATOR = VARIANT_KEY_SEPARATOR;\n\n/** Options for {@link discoverGraph}. Every field has a default; `discoverGraph(log)` works. */\nexport interface DiscoverGraphOptions {\n /**\n * Which elapsed time an edge measures. Defaults to `\"idle_time\"` — the wait between the\n * source completing and the target starting, which is the number a bottleneck view\n * wants. For a log of atomic events the two choices coincide.\n */\n flowTime?: FlowTime;\n /**\n * Reservoir capacity per activity and per edge. Lower it for a very wide log; raise it\n * for sharper tail statistics. `sum`/`mean`/`min`/`max` stay exact regardless.\n */\n maxDurationSamples?: number;\n}\n\ninterface ActivityAccumulator {\n instances: number;\n cases: number;\n duration: DurationSampler;\n}\n\ninterface TransitionAccumulator {\n source: string;\n target: string;\n count: number;\n caseCount: number;\n duration: DurationSampler;\n}\n\n/**\n * Derive a {@link ProcessGraph} from a raw or already-normalized log.\n *\n * One pass per trace: each case's activity sequence is walked once, incrementing\n * per-activity and per-edge counters and feeding two duration samplers. Per-case\n * uniqueness (the `cases` and `caseCount` fields) is tracked with two `Set`s that are\n * cleared per case rather than a per-key `Set` of case ids, so memory stays proportional\n * to the widest trace, not to the log.\n *\n * `totals.variants` is counted from the same walk (the joined activity sequence goes into\n * a `Set`), so a caller that only needs the headline number does not also have to run\n * `extractVariants`.\n */\nexport function discoverGraph(log: AnyLog, options: DiscoverGraphOptions = {}): ProcessGraph {\n const flowTime: FlowTime = options.flowTime ?? \"idle_time\";\n const capacity = options.maxDurationSamples ?? DURATION_SAMPLE_CAP;\n const normalized = asNormalizedLog(log);\n\n const activities = new Map<string, ActivityAccumulator>();\n const transitions = new Map<string, TransitionAccumulator>();\n const startActivities = new Map<string, number>();\n const endActivities = new Map<string, number>();\n const variantKeys = new Set<string>();\n\n // Sampler seeds come from a creation counter, never from a clock or from entropy: the\n // iteration order over a given log is fixed, so the same log always seeds the same\n // samplers and the reservoir retains the same rows.\n let samplerIndex = 0;\n const nextSeed = (): number => {\n samplerIndex += 1;\n return (0x9e3779b9 ^ Math.imul(samplerIndex, 0x9e3779b1)) >>> 0;\n };\n\n let events = 0;\n const seenActivities = new Set<string>();\n const seenEdges = new Set<string>();\n\n for (const kase of normalized.cases) {\n const trace = kase.events;\n if (trace.length === 0) continue;\n events += trace.length;\n seenActivities.clear();\n seenEdges.clear();\n\n const sequence = new Array<string>(trace.length);\n\n for (let i = 0; i < trace.length; i += 1) {\n const event = trace[i] as NormalizedEvent;\n const name = event.activity;\n sequence[i] = name;\n\n let activity = activities.get(name);\n if (activity === undefined) {\n activity = { instances: 0, cases: 0, duration: new DurationSampler(nextSeed(), capacity) };\n activities.set(name, activity);\n }\n activity.instances += 1;\n activity.duration.add(event.duration);\n if (!seenActivities.has(name)) {\n seenActivities.add(name);\n activity.cases += 1;\n }\n\n if (i === 0) continue;\n const previous = trace[i - 1] as NormalizedEvent;\n const source = previous.activity;\n const key = `${source}${EDGE_KEY_SEPARATOR}${name}`;\n let edge = transitions.get(key);\n if (edge === undefined) {\n edge = {\n source,\n target: name,\n count: 0,\n caseCount: 0,\n duration: new DurationSampler(nextSeed(), capacity),\n };\n transitions.set(key, edge);\n }\n edge.count += 1;\n edge.duration.add(\n flowTime === \"inter_start_time\" ? event.start - previous.start : event.start - previous.end,\n );\n if (!seenEdges.has(key)) {\n seenEdges.add(key);\n edge.caseCount += 1;\n }\n }\n\n const first = trace[0] as NormalizedEvent;\n const last = trace[trace.length - 1] as NormalizedEvent;\n startActivities.set(first.activity, (startActivities.get(first.activity) ?? 0) + 1);\n endActivities.set(last.activity, (endActivities.get(last.activity) ?? 0) + 1);\n variantKeys.add(sequence.join(VARIANT_KEY_SEPARATOR));\n }\n\n const activityList: ActivityStats[] = [];\n for (const [id, accumulator] of activities) {\n activityList.push({\n id,\n label: id,\n instances: accumulator.instances,\n cases: accumulator.cases,\n isStart: startActivities.has(id),\n isEnd: endActivities.has(id),\n duration: accumulator.duration.stats(),\n });\n }\n // Deterministic, reader-friendly order: busiest first, ties broken by name, so the same\n // log always yields the same array in the same positions (downstream items snapshot it).\n activityList.sort((a, b) => b.instances - a.instances || compareStrings(a.id, b.id));\n\n const transitionList: TransitionStats[] = [];\n for (const accumulator of transitions.values()) {\n transitionList.push({\n source: accumulator.source,\n target: accumulator.target,\n count: accumulator.count,\n caseCount: accumulator.caseCount,\n duration: accumulator.duration.stats(),\n isSelfLoop: accumulator.source === accumulator.target,\n isBackEdge: false,\n });\n }\n transitionList.sort(\n (a, b) =>\n b.count - a.count || compareStrings(a.source, b.source) || compareStrings(a.target, b.target),\n );\n\n return {\n activities: activityList,\n transitions: transitionList,\n startActivities: toSortedRecord(startActivities),\n endActivities: toSortedRecord(endActivities),\n totals: { cases: normalized.cases.length, events, variants: variantKeys.size },\n };\n}\n\n/** Code-unit comparison — locale-independent, so the order is the same on every machine. */\nfunction compareStrings(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\n/** Key order is part of the output, so it is sorted rather than left to insertion order. */\nfunction toSortedRecord(counts: Map<string, number>): Record<string, number> {\n const out: Record<string, number> = {};\n for (const key of [...counts.keys()].sort()) out[key] = counts.get(key) as number;\n return out;\n}\n","/**\n * Composition-primitive stand-ins + fixture helper for `@elabs-ai/components-process/test` —\n * RM-053.\n *\n * `@elabs-ai/components-charts/src/test/primitives.tsx` exists because Vitest's `vi.mock`\n * factory proxy throws on ANY omitted export the moment consumer code reads the binding — so\n * every composition primitive a real container renders declaratively (`<Line>`, `<XAxis>`, …)\n * needs an inert stand-in here too, not only the containers themselves. The process package's\n * public barrel (`src/index.ts`) ships no components yet (RM-051/052/054 land the first ones),\n * so there is currently nothing to namespace-complete — this file is the documented,\n * intentionally-near-empty placeholder that keeps the module shape identical to the charts\n * package's, ready for RM-051 onward to append inert stand-ins here as real composition\n * primitives land.\n *\n * What it does ship today is {@link withProcessFixture} — a small assembly helper, not a\n * render stand-in (Testing Library's own `render()` already renders JSX; nothing here needs to\n * wrap it). It takes a raw `EventLog` — typically {@link generateSyntheticLog} or the\n * BPI-2012-shaped fixture generator (`core/fixtures/generate-bpi-2012-subset.ts`) — and derives\n * the graph and variants a story or test usually wants alongside it, once.\n */\nimport { discoverGraph } from \"../core/discover-graph\";\nimport { extractVariants } from \"../core/extract-variants\";\nimport type { EventLog, ProcessGraph, Variant } from \"../core/types\";\n\n/** The derived triple most process stories/tests want from one raw log. */\nexport interface ProcessFixture {\n log: EventLog;\n graph: ProcessGraph;\n variants: Variant[];\n}\n\n/** Derive {@link ProcessFixture} from a raw event log. Pure — no caching, no I/O. */\nexport function withProcessFixture(log: EventLog): ProcessFixture {\n return { log, graph: discoverGraph(log), variants: extractVariants(log) };\n}\n"],"mappings":"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","names":[]}