@bike4mind/cli 0.2.29-cli-resume-command.18763 → 0.2.29-feature-b4m-pi.18846

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.
@@ -0,0 +1,765 @@
1
+ #!/usr/bin/env node
2
+
3
+ // ../../b4m-core/packages/services/dist/src/latticeService/DependencyTracker.js
4
+ var DependencyTracker = class {
5
+ constructor() {
6
+ this.nodes = /* @__PURE__ */ new Map();
7
+ this.topologicalOrder = null;
8
+ this.hasChanges = true;
9
+ }
10
+ /**
11
+ * Build the dependency graph from a rules store
12
+ */
13
+ build(rulesStore) {
14
+ this.nodes.clear();
15
+ this.topologicalOrder = null;
16
+ this.hasChanges = true;
17
+ for (const rule of rulesStore.rules) {
18
+ if (!rule.enabled)
19
+ continue;
20
+ this.nodes.set(rule.id, {
21
+ ruleId: rule.id,
22
+ dependencies: new Set(rule.dependencies),
23
+ dependents: /* @__PURE__ */ new Set()
24
+ });
25
+ }
26
+ for (const node of this.nodes.values()) {
27
+ for (const depId of node.dependencies) {
28
+ const depNode = this.nodes.get(depId);
29
+ if (depNode) {
30
+ depNode.dependents.add(node.ruleId);
31
+ }
32
+ }
33
+ }
34
+ }
35
+ /**
36
+ * Add a single rule to the graph
37
+ */
38
+ addRule(rule) {
39
+ if (!rule.enabled)
40
+ return;
41
+ this.nodes.set(rule.id, {
42
+ ruleId: rule.id,
43
+ dependencies: new Set(rule.dependencies),
44
+ dependents: /* @__PURE__ */ new Set()
45
+ });
46
+ for (const depId of rule.dependencies) {
47
+ const depNode = this.nodes.get(depId);
48
+ if (depNode) {
49
+ depNode.dependents.add(rule.id);
50
+ }
51
+ }
52
+ this.hasChanges = true;
53
+ this.topologicalOrder = null;
54
+ }
55
+ /**
56
+ * Remove a rule from the graph
57
+ */
58
+ removeRule(ruleId) {
59
+ const node = this.nodes.get(ruleId);
60
+ if (!node)
61
+ return;
62
+ for (const depId of node.dependencies) {
63
+ const depNode = this.nodes.get(depId);
64
+ if (depNode) {
65
+ depNode.dependents.delete(ruleId);
66
+ }
67
+ }
68
+ for (const dependentId of node.dependents) {
69
+ const dependentNode = this.nodes.get(dependentId);
70
+ if (dependentNode) {
71
+ dependentNode.dependencies.delete(ruleId);
72
+ }
73
+ }
74
+ this.nodes.delete(ruleId);
75
+ this.hasChanges = true;
76
+ this.topologicalOrder = null;
77
+ }
78
+ /**
79
+ * Update dependencies for a rule
80
+ */
81
+ updateDependencies(ruleId, newDependencies) {
82
+ const node = this.nodes.get(ruleId);
83
+ if (!node)
84
+ return;
85
+ for (const oldDepId of node.dependencies) {
86
+ const oldDepNode = this.nodes.get(oldDepId);
87
+ if (oldDepNode) {
88
+ oldDepNode.dependents.delete(ruleId);
89
+ }
90
+ }
91
+ node.dependencies = new Set(newDependencies);
92
+ for (const newDepId of newDependencies) {
93
+ const newDepNode = this.nodes.get(newDepId);
94
+ if (newDepNode) {
95
+ newDepNode.dependents.add(ruleId);
96
+ }
97
+ }
98
+ this.hasChanges = true;
99
+ this.topologicalOrder = null;
100
+ }
101
+ /**
102
+ * Validate the dependency graph for cycles
103
+ */
104
+ validate() {
105
+ const errors = [];
106
+ const cycles = [];
107
+ const visited = /* @__PURE__ */ new Set();
108
+ const recursionStack = /* @__PURE__ */ new Set();
109
+ const path = [];
110
+ const dfs = (ruleId) => {
111
+ visited.add(ruleId);
112
+ recursionStack.add(ruleId);
113
+ path.push(ruleId);
114
+ const node = this.nodes.get(ruleId);
115
+ if (node) {
116
+ for (const depId of node.dependencies) {
117
+ if (!visited.has(depId)) {
118
+ if (dfs(depId))
119
+ return true;
120
+ } else if (recursionStack.has(depId)) {
121
+ const cycleStart = path.indexOf(depId);
122
+ const cycle = [...path.slice(cycleStart), depId];
123
+ cycles.push(cycle);
124
+ errors.push({
125
+ type: "CIRCULAR_DEPENDENCY",
126
+ message: `Circular dependency detected: ${cycle.join(" \u2192 ")}`,
127
+ context: {
128
+ relatedRules: cycle
129
+ }
130
+ });
131
+ return true;
132
+ }
133
+ }
134
+ }
135
+ path.pop();
136
+ recursionStack.delete(ruleId);
137
+ return false;
138
+ };
139
+ for (const ruleId of this.nodes.keys()) {
140
+ if (!visited.has(ruleId)) {
141
+ dfs(ruleId);
142
+ }
143
+ }
144
+ return {
145
+ isValid: cycles.length === 0,
146
+ errors,
147
+ cycles
148
+ };
149
+ }
150
+ /**
151
+ * Get topological order for computation (dependencies before dependents)
152
+ */
153
+ getComputationOrder() {
154
+ if (this.topologicalOrder && !this.hasChanges) {
155
+ return this.topologicalOrder;
156
+ }
157
+ const inDegree = /* @__PURE__ */ new Map();
158
+ const queue = [];
159
+ const result = [];
160
+ for (const [ruleId, node] of this.nodes) {
161
+ inDegree.set(ruleId, node.dependencies.size);
162
+ if (node.dependencies.size === 0) {
163
+ queue.push(ruleId);
164
+ }
165
+ }
166
+ while (queue.length > 0) {
167
+ const ruleId = queue.shift();
168
+ result.push(ruleId);
169
+ const node = this.nodes.get(ruleId);
170
+ if (node) {
171
+ for (const dependentId of node.dependents) {
172
+ const degree = inDegree.get(dependentId) - 1;
173
+ inDegree.set(dependentId, degree);
174
+ if (degree === 0) {
175
+ queue.push(dependentId);
176
+ }
177
+ }
178
+ }
179
+ }
180
+ if (result.length !== this.nodes.size) {
181
+ console.warn("Dependency graph contains cycles; returning partial order");
182
+ }
183
+ this.topologicalOrder = result;
184
+ this.hasChanges = false;
185
+ return result;
186
+ }
187
+ /**
188
+ * Get all rules affected by changes to a specific rule
189
+ */
190
+ getAffectedBy(ruleId) {
191
+ const node = this.nodes.get(ruleId);
192
+ if (!node) {
193
+ return { directlyAffected: [], transitivelyAffected: [] };
194
+ }
195
+ const directlyAffected = Array.from(node.dependents);
196
+ const transitivelyAffected = /* @__PURE__ */ new Set();
197
+ const queue = [...directlyAffected];
198
+ while (queue.length > 0) {
199
+ const currentId = queue.shift();
200
+ if (transitivelyAffected.has(currentId))
201
+ continue;
202
+ transitivelyAffected.add(currentId);
203
+ const currentNode = this.nodes.get(currentId);
204
+ if (currentNode) {
205
+ for (const dependentId of currentNode.dependents) {
206
+ if (!transitivelyAffected.has(dependentId)) {
207
+ queue.push(dependentId);
208
+ }
209
+ }
210
+ }
211
+ }
212
+ return {
213
+ directlyAffected,
214
+ transitivelyAffected: Array.from(transitivelyAffected)
215
+ };
216
+ }
217
+ /**
218
+ * Get direct dependencies of a rule
219
+ */
220
+ getDependencies(ruleId) {
221
+ const node = this.nodes.get(ruleId);
222
+ return node ? Array.from(node.dependencies) : [];
223
+ }
224
+ /**
225
+ * Get direct dependents of a rule
226
+ */
227
+ getDependents(ruleId) {
228
+ const node = this.nodes.get(ruleId);
229
+ return node ? Array.from(node.dependents) : [];
230
+ }
231
+ /**
232
+ * Check if adding a dependency would create a cycle
233
+ */
234
+ wouldCreateCycle(fromRuleId, toRuleId) {
235
+ const visited = /* @__PURE__ */ new Set();
236
+ const queue = [toRuleId];
237
+ while (queue.length > 0) {
238
+ const currentId = queue.shift();
239
+ if (currentId === fromRuleId)
240
+ return true;
241
+ if (visited.has(currentId))
242
+ continue;
243
+ visited.add(currentId);
244
+ const node = this.nodes.get(currentId);
245
+ if (node) {
246
+ for (const depId of node.dependencies) {
247
+ if (!visited.has(depId)) {
248
+ queue.push(depId);
249
+ }
250
+ }
251
+ }
252
+ }
253
+ return false;
254
+ }
255
+ /**
256
+ * Get all rule IDs in the graph
257
+ */
258
+ getAllRuleIds() {
259
+ return Array.from(this.nodes.keys());
260
+ }
261
+ /**
262
+ * Get the number of rules in the graph
263
+ */
264
+ size() {
265
+ return this.nodes.size;
266
+ }
267
+ /**
268
+ * Clear the entire graph
269
+ */
270
+ clear() {
271
+ this.nodes.clear();
272
+ this.topologicalOrder = null;
273
+ this.hasChanges = true;
274
+ }
275
+ /**
276
+ * Export graph for debugging/visualization
277
+ */
278
+ toDebugString() {
279
+ const lines = ["Dependency Graph:"];
280
+ for (const [ruleId, node] of this.nodes) {
281
+ const deps = Array.from(node.dependencies).join(", ") || "(none)";
282
+ const dependents = Array.from(node.dependents).join(", ") || "(none)";
283
+ lines.push(` ${ruleId}:`);
284
+ lines.push(` depends on: ${deps}`);
285
+ lines.push(` depended by: ${dependents}`);
286
+ }
287
+ return lines.join("\n");
288
+ }
289
+ };
290
+ function createDependencyTracker(rulesStore) {
291
+ const tracker = new DependencyTracker();
292
+ if (rulesStore) {
293
+ tracker.build(rulesStore);
294
+ }
295
+ return tracker;
296
+ }
297
+
298
+ // ../../b4m-core/packages/services/dist/src/latticeService/HydrationEngine.js
299
+ var HydrationEngine = class {
300
+ constructor() {
301
+ this.dependencyTracker = createDependencyTracker();
302
+ }
303
+ /**
304
+ * Hydrate (compute) all values from data and rules
305
+ */
306
+ hydrate(data, rules, options = {}) {
307
+ const startTime = performance.now();
308
+ const errors = [];
309
+ this.dependencyTracker.build(rules);
310
+ const validation = this.dependencyTracker.validate();
311
+ if (!validation.isValid) {
312
+ return {
313
+ values: {},
314
+ errors: validation.errors,
315
+ duration: performance.now() - startTime,
316
+ rulesEvaluated: 0
317
+ };
318
+ }
319
+ const computedValues = this.initializeFromData(data, options.scenario);
320
+ const context = {
321
+ data,
322
+ rules,
323
+ computedValues,
324
+ scenario: options.scenario,
325
+ errors
326
+ };
327
+ let ruleOrder = this.dependencyTracker.getComputationOrder();
328
+ if (options.partialRuleIds && options.partialRuleIds.length > 0) {
329
+ const neededRules = this.getNeededRules(options.partialRuleIds);
330
+ ruleOrder = ruleOrder.filter((id) => neededRules.has(id));
331
+ }
332
+ let rulesEvaluated = 0;
333
+ for (const ruleId of ruleOrder) {
334
+ const rule = rules.rules.find((r) => r.id === ruleId);
335
+ if (!rule || !rule.enabled)
336
+ continue;
337
+ try {
338
+ this.evaluateRule(rule, context);
339
+ rulesEvaluated++;
340
+ } catch (error) {
341
+ errors.push({
342
+ type: "INVALID_OPERATION",
343
+ message: `Error evaluating rule "${rule.name}": ${error instanceof Error ? error.message : String(error)}`,
344
+ context: { relatedRules: [ruleId] }
345
+ });
346
+ }
347
+ }
348
+ return {
349
+ values: computedValues,
350
+ errors,
351
+ duration: performance.now() - startTime,
352
+ rulesEvaluated
353
+ };
354
+ }
355
+ /**
356
+ * Initialize computed values from base data (including scenario overrides)
357
+ */
358
+ initializeFromData(data, scenario) {
359
+ const values = {};
360
+ for (const entity of data.entities) {
361
+ values[entity.id] = {};
362
+ for (const attr of entity.attributes) {
363
+ if (!attr.isComputed) {
364
+ values[entity.id][attr.key] = {
365
+ value: attr.value,
366
+ computedByRuleId: "base",
367
+ computedAt: /* @__PURE__ */ new Date()
368
+ };
369
+ }
370
+ }
371
+ }
372
+ if (scenario) {
373
+ for (const override of scenario.overrides) {
374
+ if (!values[override.entityId]) {
375
+ values[override.entityId] = {};
376
+ }
377
+ values[override.entityId][override.attributeKey] = {
378
+ value: override.value,
379
+ computedByRuleId: `scenario:${scenario.id}`,
380
+ computedAt: /* @__PURE__ */ new Date()
381
+ };
382
+ }
383
+ }
384
+ return values;
385
+ }
386
+ /**
387
+ * Get all rules needed to compute the given rules (including dependencies)
388
+ */
389
+ getNeededRules(ruleIds) {
390
+ const needed = /* @__PURE__ */ new Set();
391
+ const addWithDependencies = (ruleId) => {
392
+ if (needed.has(ruleId))
393
+ return;
394
+ needed.add(ruleId);
395
+ const deps = this.dependencyTracker.getDependencies(ruleId);
396
+ for (const depId of deps) {
397
+ addWithDependencies(depId);
398
+ }
399
+ };
400
+ for (const ruleId of ruleIds) {
401
+ addWithDependencies(ruleId);
402
+ }
403
+ return needed;
404
+ }
405
+ /**
406
+ * Evaluate a single rule and store results
407
+ */
408
+ evaluateRule(rule, context) {
409
+ const { definition } = rule;
410
+ const inputValues = this.resolveInputs(definition.inputs, context);
411
+ if (definition.conditions && definition.conditions.length > 0) {
412
+ const conditionsMet = this.evaluateConditions(definition.conditions, context);
413
+ if (!conditionsMet)
414
+ return;
415
+ }
416
+ const result = this.applyOperation(definition.operation, inputValues, context);
417
+ const { targetEntityId, targetAttribute } = definition.output;
418
+ if (!context.computedValues[targetEntityId]) {
419
+ context.computedValues[targetEntityId] = {};
420
+ }
421
+ context.computedValues[targetEntityId][targetAttribute] = {
422
+ value: result,
423
+ computedByRuleId: rule.id,
424
+ computedAt: /* @__PURE__ */ new Date()
425
+ };
426
+ }
427
+ /**
428
+ * Resolve input references to actual values
429
+ */
430
+ resolveInputs(inputs, context) {
431
+ const values = [];
432
+ for (const input of inputs) {
433
+ switch (input.type) {
434
+ case "literal":
435
+ values.push(this.parseLiteral(input.ref));
436
+ break;
437
+ case "attribute":
438
+ case "entity": {
439
+ const [entityId, attrKey] = input.selector ? [input.ref, input.selector] : input.ref.split(".");
440
+ const entityValues = context.computedValues[entityId];
441
+ if (entityValues && attrKey in entityValues) {
442
+ values.push(entityValues[attrKey].value);
443
+ } else {
444
+ values.push(null);
445
+ }
446
+ break;
447
+ }
448
+ case "rule": {
449
+ const rule = context.rules.rules.find((r) => r.id === input.ref);
450
+ if (rule) {
451
+ const { targetEntityId, targetAttribute } = rule.definition.output;
452
+ const entityValues = context.computedValues[targetEntityId];
453
+ if (entityValues && targetAttribute in entityValues) {
454
+ values.push(entityValues[targetAttribute].value);
455
+ } else {
456
+ values.push(null);
457
+ }
458
+ } else {
459
+ values.push(null);
460
+ }
461
+ break;
462
+ }
463
+ case "range": {
464
+ const rangeValues = this.resolveRange(input.ref, input.selector, context);
465
+ values.push(...rangeValues);
466
+ break;
467
+ }
468
+ default:
469
+ values.push(null);
470
+ }
471
+ }
472
+ return values;
473
+ }
474
+ /**
475
+ * Resolve a range reference to multiple values
476
+ */
477
+ resolveRange(entityPattern, selector, context) {
478
+ const values = [];
479
+ if (entityPattern.includes("*")) {
480
+ for (const [entityId, entityValues] of Object.entries(context.computedValues)) {
481
+ if (this.matchesPattern(entityId, entityPattern)) {
482
+ if (selector === "*") {
483
+ values.push(...Object.values(entityValues).map((v) => v.value));
484
+ } else if (selector) {
485
+ if (selector in entityValues) {
486
+ values.push(entityValues[selector].value);
487
+ }
488
+ }
489
+ }
490
+ }
491
+ } else {
492
+ const entityValues = context.computedValues[entityPattern];
493
+ if (entityValues) {
494
+ if (selector === "*") {
495
+ values.push(...Object.values(entityValues).map((v) => v.value));
496
+ }
497
+ }
498
+ }
499
+ return values;
500
+ }
501
+ /**
502
+ * Check if an entity ID matches a pattern
503
+ */
504
+ matchesPattern(entityId, pattern) {
505
+ if (pattern === "*")
506
+ return true;
507
+ if (!pattern.includes("*"))
508
+ return entityId === pattern;
509
+ const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
510
+ return regex.test(entityId);
511
+ }
512
+ /**
513
+ * Parse a literal string to a value
514
+ */
515
+ parseLiteral(value) {
516
+ const num = parseFloat(value);
517
+ if (!isNaN(num))
518
+ return num;
519
+ if (value.toLowerCase() === "true")
520
+ return true;
521
+ if (value.toLowerCase() === "false")
522
+ return false;
523
+ return value;
524
+ }
525
+ /**
526
+ * Evaluate rule conditions
527
+ */
528
+ evaluateConditions(conditions, context) {
529
+ if (!conditions || conditions.length === 0)
530
+ return true;
531
+ let result = true;
532
+ let currentJoin;
533
+ for (const condition of conditions) {
534
+ const leftValue = this.resolveInputs([condition.left], context)[0];
535
+ const rightValue = this.resolveInputs([condition.right], context)[0];
536
+ const conditionResult = this.evaluateComparison(leftValue, condition.operator, rightValue);
537
+ if (currentJoin === "OR") {
538
+ result = result || conditionResult;
539
+ } else {
540
+ result = result && conditionResult;
541
+ }
542
+ currentJoin = condition.logicalJoin;
543
+ }
544
+ return result;
545
+ }
546
+ /**
547
+ * Evaluate a comparison
548
+ */
549
+ evaluateComparison(left, operator, right) {
550
+ switch (operator) {
551
+ case "==":
552
+ return left === right;
553
+ case "!=":
554
+ return left !== right;
555
+ case ">":
556
+ return left > right;
557
+ case "<":
558
+ return left < right;
559
+ case ">=":
560
+ return left >= right;
561
+ case "<=":
562
+ return left <= right;
563
+ case "contains":
564
+ return String(left).includes(String(right));
565
+ case "in":
566
+ return Array.isArray(right) && right.includes(left);
567
+ default:
568
+ return false;
569
+ }
570
+ }
571
+ /**
572
+ * Apply an operation to input values
573
+ */
574
+ applyOperation(operation, inputs, context) {
575
+ const numericInputs = inputs.filter((v) => v !== null && typeof v === "number").map((v) => v);
576
+ switch (operation) {
577
+ // Arithmetic
578
+ case "ADD":
579
+ return numericInputs.reduce((a, b) => a + b, 0);
580
+ case "SUBTRACT":
581
+ if (numericInputs.length === 0)
582
+ return 0;
583
+ return numericInputs.slice(1).reduce((a, b) => a - b, numericInputs[0]);
584
+ case "MULTIPLY":
585
+ return numericInputs.reduce((a, b) => a * b, 1);
586
+ case "DIVIDE":
587
+ if (numericInputs.length < 2 || numericInputs[1] === 0) {
588
+ context.errors.push({
589
+ type: "DIVISION_BY_ZERO",
590
+ message: "Division by zero"
591
+ });
592
+ return null;
593
+ }
594
+ return numericInputs[0] / numericInputs[1];
595
+ case "ABS":
596
+ return numericInputs.length > 0 ? Math.abs(numericInputs[0]) : 0;
597
+ case "ROUND":
598
+ if (numericInputs.length === 0)
599
+ return 0;
600
+ const decimals = numericInputs[1] ?? 0;
601
+ const factor = Math.pow(10, decimals);
602
+ return Math.round(numericInputs[0] * factor) / factor;
603
+ case "FLOOR":
604
+ return numericInputs.length > 0 ? Math.floor(numericInputs[0]) : 0;
605
+ case "CEIL":
606
+ return numericInputs.length > 0 ? Math.ceil(numericInputs[0]) : 0;
607
+ case "POWER":
608
+ if (numericInputs.length < 2)
609
+ return numericInputs[0] ?? 0;
610
+ return Math.pow(numericInputs[0], numericInputs[1]);
611
+ case "SQRT":
612
+ return numericInputs.length > 0 ? Math.sqrt(numericInputs[0]) : 0;
613
+ // Aggregation
614
+ case "SUM":
615
+ return numericInputs.reduce((a, b) => a + b, 0);
616
+ case "AVERAGE":
617
+ if (numericInputs.length === 0)
618
+ return 0;
619
+ return numericInputs.reduce((a, b) => a + b, 0) / numericInputs.length;
620
+ case "MIN":
621
+ return numericInputs.length > 0 ? Math.min(...numericInputs) : 0;
622
+ case "MAX":
623
+ return numericInputs.length > 0 ? Math.max(...numericInputs) : 0;
624
+ case "COUNT":
625
+ return inputs.filter((v) => v !== null).length;
626
+ case "MEDIAN":
627
+ if (numericInputs.length === 0)
628
+ return 0;
629
+ const sorted = [...numericInputs].sort((a, b) => a - b);
630
+ const mid = Math.floor(sorted.length / 2);
631
+ return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
632
+ // Logical
633
+ case "IF":
634
+ return inputs[0] ? inputs[1] : inputs[2];
635
+ case "AND":
636
+ return inputs.every((v) => Boolean(v));
637
+ case "OR":
638
+ return inputs.some((v) => Boolean(v));
639
+ case "NOT":
640
+ return !inputs[0];
641
+ case "EQUALS":
642
+ return inputs.length >= 2 && inputs[0] === inputs[1];
643
+ case "GREATER_THAN":
644
+ return inputs.length >= 2 && inputs[0] > inputs[1];
645
+ case "LESS_THAN":
646
+ return inputs.length >= 2 && inputs[0] < inputs[1];
647
+ case "GREATER_THAN_OR_EQUAL":
648
+ return inputs.length >= 2 && inputs[0] >= inputs[1];
649
+ case "LESS_THAN_OR_EQUAL":
650
+ return inputs.length >= 2 && inputs[0] <= inputs[1];
651
+ case "BETWEEN":
652
+ if (inputs.length < 3)
653
+ return false;
654
+ const val = inputs[0];
655
+ const min = inputs[1];
656
+ const max = inputs[2];
657
+ return val >= min && val <= max;
658
+ // Financial
659
+ case "PERCENT_OF":
660
+ if (numericInputs.length < 2 || numericInputs[1] === 0)
661
+ return 0;
662
+ return numericInputs[0] / numericInputs[1] * 100;
663
+ case "GROWTH_RATE":
664
+ if (numericInputs.length < 2 || numericInputs[1] === 0)
665
+ return 0;
666
+ return (numericInputs[0] - numericInputs[1]) / numericInputs[1] * 100;
667
+ case "NPV": {
668
+ if (numericInputs.length < 2)
669
+ return 0;
670
+ const rate = numericInputs[0];
671
+ const cashflows = numericInputs.slice(1);
672
+ return cashflows.reduce((npv, cf, i) => npv + cf / Math.pow(1 + rate, i + 1), 0);
673
+ }
674
+ case "IRR":
675
+ return this.calculateIRR(numericInputs);
676
+ case "PMT": {
677
+ if (numericInputs.length < 3)
678
+ return 0;
679
+ const [rate, nper, pv, fv = 0, type = 0] = numericInputs;
680
+ if (rate === 0)
681
+ return -(pv + fv) / nper;
682
+ const pvif = Math.pow(1 + rate, nper);
683
+ let pmt = rate * (pv * pvif + fv) / (pvif - 1);
684
+ if (type === 1)
685
+ pmt = pmt / (1 + rate);
686
+ return -pmt;
687
+ }
688
+ case "FV": {
689
+ if (numericInputs.length < 3)
690
+ return 0;
691
+ const [rate, nper, pmt, pv = 0, type = 0] = numericInputs;
692
+ if (rate === 0)
693
+ return -(pv + pmt * nper);
694
+ const pvif = Math.pow(1 + rate, nper);
695
+ let fv = -pv * pvif - pmt * (pvif - 1) / rate;
696
+ if (type === 1)
697
+ fv = fv - pmt * rate * nper;
698
+ return fv;
699
+ }
700
+ case "PV": {
701
+ if (numericInputs.length < 3)
702
+ return 0;
703
+ const [rate, nper, pmt, fv = 0, type = 0] = numericInputs;
704
+ if (rate === 0)
705
+ return -(fv + pmt * nper);
706
+ const pvif = Math.pow(1 + rate, nper);
707
+ let pv = (-fv - pmt * (pvif - 1) / rate) / pvif;
708
+ if (type === 1)
709
+ pv = pv / (1 + rate);
710
+ return pv;
711
+ }
712
+ // Special
713
+ case "REFERENCE":
714
+ return inputs[0];
715
+ // Pass through
716
+ case "LOOKUP":
717
+ if (inputs.length < 2)
718
+ return null;
719
+ const index = inputs[0];
720
+ return inputs[Math.min(index + 1, inputs.length - 1)];
721
+ default:
722
+ context.errors.push({
723
+ type: "INVALID_OPERATION",
724
+ message: `Unknown operation: ${operation}`
725
+ });
726
+ return null;
727
+ }
728
+ }
729
+ /**
730
+ * Calculate IRR using Newton's method
731
+ */
732
+ calculateIRR(cashflows, guess = 0.1, maxIterations = 100, tolerance = 1e-4) {
733
+ let rate = guess;
734
+ for (let i = 0; i < maxIterations; i++) {
735
+ let npv = 0;
736
+ let dnpv = 0;
737
+ for (let j = 0; j < cashflows.length; j++) {
738
+ const factor = Math.pow(1 + rate, j);
739
+ npv += cashflows[j] / factor;
740
+ dnpv -= j * cashflows[j] / Math.pow(1 + rate, j + 1);
741
+ }
742
+ if (Math.abs(npv) < tolerance) {
743
+ return rate;
744
+ }
745
+ if (dnpv === 0)
746
+ break;
747
+ rate = rate - npv / dnpv;
748
+ }
749
+ return rate;
750
+ }
751
+ /**
752
+ * Get the dependency tracker (for advanced use cases)
753
+ */
754
+ getDependencyTracker() {
755
+ return this.dependencyTracker;
756
+ }
757
+ };
758
+ function createHydrationEngine() {
759
+ return new HydrationEngine();
760
+ }
761
+
762
+ export {
763
+ HydrationEngine,
764
+ createHydrationEngine
765
+ };