@diagrammo/dgmo 0.45.0 → 0.47.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 (250) hide show
  1. package/dist/advanced.cjs +31 -25
  2. package/dist/advanced.d.cts +3 -855
  3. package/dist/advanced.d.ts +3 -855
  4. package/dist/advanced.js +541 -70101
  5. package/dist/analyzer-UYKBXAKR.js +15 -0
  6. package/dist/auto.cjs +500 -341
  7. package/dist/auto.css +342 -159
  8. package/dist/auto.d.cts +10 -8
  9. package/dist/auto.d.ts +10 -8
  10. package/dist/auto.js +373 -265
  11. package/dist/auto.mjs +500 -341
  12. package/dist/block.cjs +31 -25
  13. package/dist/block.js +83 -69411
  14. package/dist/calculator-LDYQCDDY.js +11 -0
  15. package/dist/charts-d3-QOZHKTCT.js +25 -0
  16. package/dist/chunk-2DXMZRWJ.js +800 -0
  17. package/dist/chunk-2LACINWN.js +786 -0
  18. package/dist/chunk-33LCUH4H.js +893 -0
  19. package/dist/chunk-3CIOJ5YJ.js +35 -0
  20. package/dist/chunk-3HDBAXF5.js +86 -0
  21. package/dist/chunk-3JLKYGBQ.js +290 -0
  22. package/dist/chunk-3MQQNXPD.js +415 -0
  23. package/dist/chunk-3R5POCTZ.js +967 -0
  24. package/dist/chunk-3SMGIJDD.js +112 -0
  25. package/dist/chunk-44CM72SQ.js +409 -0
  26. package/dist/chunk-4GRI7MEB.js +1604 -0
  27. package/dist/chunk-4OHACBGX.js +606 -0
  28. package/dist/chunk-5DH3KYFH.js +21 -0
  29. package/dist/chunk-5MLYVTYM.js +76 -0
  30. package/dist/chunk-5QXL4NYS.js +553 -0
  31. package/dist/chunk-5WRI5ZAA.js +31 -0
  32. package/dist/chunk-6EK55ART.js +117 -0
  33. package/dist/chunk-6FZYCKXB.js +2065 -0
  34. package/dist/chunk-6HO5ZTXK.js +277 -0
  35. package/dist/chunk-6KFWC4CO.js +7 -0
  36. package/dist/chunk-6MTWIQVO.js +121 -0
  37. package/dist/chunk-6NYWYNBI.js +329 -0
  38. package/dist/chunk-6SM5NJQH.js +620 -0
  39. package/dist/chunk-725LDASD.js +422 -0
  40. package/dist/chunk-76OWZ3BA.js +130 -0
  41. package/dist/chunk-7JEGTRLD.js +338 -0
  42. package/dist/chunk-A44USUV7.js +923 -0
  43. package/dist/chunk-A4GOY3PE.js +25 -0
  44. package/dist/chunk-A4VFQH5G.js +251 -0
  45. package/dist/chunk-AELOBFW5.js +375 -0
  46. package/dist/chunk-AN2MOO5T.js +522 -0
  47. package/dist/chunk-BMR4CIEO.js +41 -0
  48. package/dist/chunk-BS7SV7C3.js +51 -0
  49. package/dist/chunk-BUPDIHML.js +613 -0
  50. package/dist/chunk-BUVJFRPY.js +887 -0
  51. package/dist/chunk-C3Z4DX3V.js +112 -0
  52. package/dist/chunk-CI2G3OEB.js +1818 -0
  53. package/dist/chunk-CKNVO24D.js +54 -0
  54. package/dist/chunk-CUVHQHZ7.js +391 -0
  55. package/dist/chunk-CXK4DZH3.js +114 -0
  56. package/dist/chunk-DKEPS5LH.js +315 -0
  57. package/dist/chunk-DU7W62N5.js +487 -0
  58. package/dist/chunk-DZAQBW35.js +751 -0
  59. package/dist/chunk-EB6FPQUP.js +316 -0
  60. package/dist/chunk-EMS2SK26.js +376 -0
  61. package/dist/chunk-ERLBPDGU.js +510 -0
  62. package/dist/chunk-EVCVDTXE.js +732 -0
  63. package/dist/chunk-FCIIHT24.js +384 -0
  64. package/dist/chunk-FFUN5D66.js +97 -0
  65. package/dist/chunk-FITOLXSX.js +714 -0
  66. package/dist/chunk-FMDSF3OV.js +68 -0
  67. package/dist/chunk-FNB3PNGF.js +410 -0
  68. package/dist/chunk-G2H74XJZ.js +2104 -0
  69. package/dist/chunk-GMR5MFHJ.js +339 -0
  70. package/dist/chunk-H6PI3KJT.js +305 -0
  71. package/dist/chunk-I46X5VUA.js +59 -0
  72. package/dist/chunk-I5IGB56C.js +255 -0
  73. package/dist/chunk-IH6ZMVQ5.js +3950 -0
  74. package/dist/chunk-IJZFN3HM.js +463 -0
  75. package/dist/chunk-IPWPWM6R.js +3079 -0
  76. package/dist/chunk-IYULMRBE.js +23 -0
  77. package/dist/chunk-IZMRON6C.js +167 -0
  78. package/dist/chunk-J5FIXQRT.js +813 -0
  79. package/dist/chunk-JED6ZDEE.js +73 -0
  80. package/dist/chunk-JEUJOX5X.js +136 -0
  81. package/dist/chunk-JM4LEPY3.js +436 -0
  82. package/dist/chunk-L3UHP6TD.js +101 -0
  83. package/dist/chunk-L66GEFB6.js +524 -0
  84. package/dist/chunk-LIOHW36Z.js +469 -0
  85. package/dist/chunk-LP7EC4MI.js +16 -0
  86. package/dist/chunk-MITI6NOL.js +548 -0
  87. package/dist/chunk-MO5YHHMQ.js +168 -0
  88. package/dist/chunk-MZSBFAPJ.js +885 -0
  89. package/dist/chunk-NNAAJVBU.js +9 -0
  90. package/dist/chunk-NOTA6TEI.js +47 -0
  91. package/dist/chunk-NURGB5Z4.js +335 -0
  92. package/dist/chunk-NX4EQS3U.js +1062 -0
  93. package/dist/chunk-NXMESY4J.js +1 -0
  94. package/dist/chunk-OJR3MQYV.js +1458 -0
  95. package/dist/chunk-OURHMODR.js +454 -0
  96. package/dist/chunk-P235SVAH.js +1057 -0
  97. package/dist/chunk-P4RH4OSP.js +1038 -0
  98. package/dist/chunk-P6T66CCZ.js +226 -0
  99. package/dist/chunk-PBAHEPLQ.js +223 -0
  100. package/dist/chunk-PBHE5UG6.js +1530 -0
  101. package/dist/chunk-PDKUANDZ.js +35 -0
  102. package/dist/chunk-PWIHZ6NJ.js +296 -0
  103. package/dist/chunk-QEBUY5NY.js +1685 -0
  104. package/dist/chunk-QLCCJEJI.js +253 -0
  105. package/dist/chunk-QOOUC2J5.js +52 -0
  106. package/dist/chunk-QYTPPDAO.js +402 -0
  107. package/dist/chunk-R3XI2VC7.js +801 -0
  108. package/dist/chunk-RCERWOJR.js +154 -0
  109. package/dist/chunk-RJE5JTVK.js +362 -0
  110. package/dist/chunk-RTLBSQ3G.js +1155 -0
  111. package/dist/chunk-RUNNI6VE.js +448 -0
  112. package/dist/chunk-RVMRZAPB.js +354 -0
  113. package/dist/chunk-RYEGFMUO.js +955 -0
  114. package/dist/chunk-RZCF2VVM.js +528 -0
  115. package/dist/chunk-SML67VPV.js +1025 -0
  116. package/dist/chunk-SQ7IY53Q.js +44 -0
  117. package/dist/chunk-SQAG3UXH.js +161 -0
  118. package/dist/chunk-SRTU6HMQ.js +1851 -0
  119. package/dist/chunk-T2OE3YQB.js +550 -0
  120. package/dist/chunk-T3UBLACL.js +299 -0
  121. package/dist/chunk-TLVIHFRO.js +296 -0
  122. package/dist/chunk-TYS6CSS5.js +52 -0
  123. package/dist/chunk-U6EQI3U6.js +130 -0
  124. package/dist/chunk-UJ4FJLBF.js +86 -0
  125. package/dist/chunk-ULOEATBZ.js +347 -0
  126. package/dist/chunk-UNCCZ3NU.js +127 -0
  127. package/dist/chunk-USZW2HHU.js +197 -0
  128. package/dist/chunk-UWN33VNX.js +1610 -0
  129. package/dist/chunk-UZ7EZW3B.js +40 -0
  130. package/dist/chunk-VOKGH627.js +37 -0
  131. package/dist/chunk-WBRZJOEC.js +204 -0
  132. package/dist/chunk-WCSOJFU3.js +380 -0
  133. package/dist/chunk-WCT3NYCV.js +160 -0
  134. package/dist/chunk-WVAJFAIA.js +529 -0
  135. package/dist/chunk-X5LCYCL5.js +394 -0
  136. package/dist/chunk-XAKEUMEJ.js +1256 -0
  137. package/dist/chunk-Y2ZE3MSG.js +590 -0
  138. package/dist/chunk-Y3J7VR52.js +646 -0
  139. package/dist/chunk-Y5QGVTVC.js +33 -0
  140. package/dist/chunk-YAA6IID3.js +548 -0
  141. package/dist/chunk-YGJTDXVR.js +692 -0
  142. package/dist/chunk-YRYKTYMQ.js +186 -0
  143. package/dist/chunk-YYVTBZZC.js +61 -0
  144. package/dist/chunk-Z5CGSFJB.js +647 -0
  145. package/dist/chunk-Z72XRXD4.js +787 -0
  146. package/dist/chunk-ZGR7ULFJ.js +978 -0
  147. package/dist/chunk-ZJEL4H2R.js +851 -0
  148. package/dist/chunk-ZO27FGJS.js +554 -0
  149. package/dist/cli.cjs +112 -112
  150. package/dist/collapse-CLPJD2RL.js +11 -0
  151. package/dist/collapse-SLINJQW6.js +9 -0
  152. package/dist/collapse-ZWUFFCK2.js +9 -0
  153. package/dist/compute-G55RG53N.js +8 -0
  154. package/dist/dgmo-router-DZCIDUO5.js +73 -0
  155. package/dist/dimensions-J3FHIMJ6.js +22 -0
  156. package/dist/element.cjs +492 -325
  157. package/dist/element.js +367 -259
  158. package/dist/element.mjs +492 -325
  159. package/dist/flowchart-parser-ZNSKIBVR.js +21 -0
  160. package/dist/flowchart-renderer-ZAEOL4DX.js +35 -0
  161. package/dist/index.cjs +34 -28
  162. package/dist/index.d.cts +3 -469
  163. package/dist/index.d.ts +3 -469
  164. package/dist/index.js +137 -69679
  165. package/dist/layout-46N4JJYK.js +17 -0
  166. package/dist/layout-5RA6QWR6.js +26 -0
  167. package/dist/layout-5T6RH5M6.js +14 -0
  168. package/dist/layout-7QXCNOVS.js +13 -0
  169. package/dist/layout-EE6GE46Y.js +17 -0
  170. package/dist/layout-G23F7LDH.js +9 -0
  171. package/dist/layout-HRBZPFCN.js +23 -0
  172. package/dist/layout-HWDSRX5E.js +20 -0
  173. package/dist/layout-HYZPWAK5.js +18 -0
  174. package/dist/layout-N76NR4ZO.js +19 -0
  175. package/dist/layout-OB3RI63S.js +14 -0
  176. package/dist/layout-S2QBYSK2.js +15 -0
  177. package/dist/layout-UM5F4DJB.js +15 -0
  178. package/dist/layout-search-OYV77HHS.js +1655 -0
  179. package/dist/load-data-EHDDZ5ZS.js +8 -0
  180. package/dist/palettes-PRJ2RI6D.js +56 -0
  181. package/dist/parser-2MFP2H2E.js +11 -0
  182. package/dist/parser-3STE62GB.js +21 -0
  183. package/dist/parser-4FL2CMDG.js +18 -0
  184. package/dist/parser-4OGOBJJ4.js +13 -0
  185. package/dist/parser-6BTRLKRZ.js +15 -0
  186. package/dist/parser-AQETEVRW.js +21 -0
  187. package/dist/parser-CFZD5IBL.js +12 -0
  188. package/dist/parser-FUSKFGZN.js +16 -0
  189. package/dist/parser-I6KXEYFK.js +13 -0
  190. package/dist/parser-IXSZZQHY.js +18 -0
  191. package/dist/parser-JAL2HD4I.js +13 -0
  192. package/dist/parser-KNSQSKQO.js +20 -0
  193. package/dist/parser-N5O4IHUN.js +13 -0
  194. package/dist/parser-NGU3ETO6.js +16 -0
  195. package/dist/parser-NRAI6O25.js +19 -0
  196. package/dist/parser-O5J4VBII.js +11 -0
  197. package/dist/parser-OV7NOBW6.js +21 -0
  198. package/dist/parser-PQ3POX45.js +23 -0
  199. package/dist/parser-TFZFBVWN.js +11 -0
  200. package/dist/parser-TRTHDFPP.js +18 -0
  201. package/dist/parser-VQP2UJ6W.js +11 -0
  202. package/dist/parser-XDVELHRK.js +13 -0
  203. package/dist/parser-XKRP7QKP.js +11 -0
  204. package/dist/parser-Y6WTBU76.js +14 -0
  205. package/dist/parser-ZSDPZYQP.js +12 -0
  206. package/dist/renderer-2XQQQ6QR.js +28 -0
  207. package/dist/renderer-5NDVNDAZ.js +26 -0
  208. package/dist/renderer-6EEZHMH4.js +26 -0
  209. package/dist/renderer-6W5462RB.js +43 -0
  210. package/dist/renderer-7MVLWWQD.js +22 -0
  211. package/dist/renderer-AWWAF6XD.js +33 -0
  212. package/dist/renderer-BYCNMBF7.js +34 -0
  213. package/dist/renderer-CLJHQPBJ.js +29 -0
  214. package/dist/renderer-GHPZMFU2.js +15 -0
  215. package/dist/renderer-JGBKUCZD.js +16 -0
  216. package/dist/renderer-KQB7UGDS.js +19 -0
  217. package/dist/renderer-PGX2TKVS.js +24 -0
  218. package/dist/renderer-QIWVHWZO.js +15 -0
  219. package/dist/renderer-RPOA5YPZ.js +35 -0
  220. package/dist/renderer-SMIBCRVB.js +24 -0
  221. package/dist/renderer-T7YKC5VP.js +28 -0
  222. package/dist/renderer-TOCOVLHS.js +37 -0
  223. package/dist/renderer-UWA5XS74.js +39 -0
  224. package/dist/renderer-VACVQYML.js +25 -0
  225. package/dist/renderer-W4M6QLIJ.js +37 -0
  226. package/dist/renderer-WZRREQ2P.js +23 -0
  227. package/dist/renderer-YY2OSF6E.js +27 -0
  228. package/dist/renderer-Z7I3W2PP.js +24 -0
  229. package/dist/renderer-ZGNTUWC4.js +22 -0
  230. package/dist/renderer-ZTR3DER3.js +19 -0
  231. package/dist/renderer-radial-C32GPJQ7.js +21 -0
  232. package/dist/resolver-NOAA7ID7.js +13 -0
  233. package/dist/state-collapse-HCGD6IVJ.js +8 -0
  234. package/dist/state-parser-Z2V23ATR.js +18 -0
  235. package/dist/state-renderer-RFYHSP4K.js +34 -0
  236. package/dist/themes-1-CuKpeH.d.cts +908 -0
  237. package/dist/themes-1-CuKpeH.d.ts +908 -0
  238. package/package.json +1 -1
  239. package/src/auto/index.ts +55 -57
  240. package/src/auto/shared.ts +142 -142
  241. package/src/auto/styles.ts +44 -199
  242. package/src/c4/renderer.ts +2 -2
  243. package/src/element/index.ts +29 -21
  244. package/src/infra/renderer.ts +1 -1
  245. package/src/render.ts +34 -14
  246. package/src/sitemap/renderer.ts +1 -1
  247. package/src/timeline/renderer.ts +1 -1
  248. package/src/utils/hover-styles.ts +6 -1
  249. package/src/utils/legend-d3.ts +1 -4
  250. package/src/utils/legend-svg.ts +1 -1
@@ -0,0 +1,1062 @@
1
+ import {
2
+ CONFIDENCE_TABLE,
3
+ addCalendarDays,
4
+ resolveConfidence,
5
+ unitToDays
6
+ } from "./chunk-L3UHP6TD.js";
7
+ import {
8
+ makeDgmoError
9
+ } from "./chunk-CXK4DZH3.js";
10
+
11
+ // src/pert/monte-carlo.ts
12
+ function lagDays(lag) {
13
+ return lag === null ? 0 : lag.amount * unitToDays(lag.unit);
14
+ }
15
+ function mulberry32(seed) {
16
+ let s = seed >>> 0;
17
+ return () => {
18
+ s = s + 1831565813 >>> 0;
19
+ let t = s;
20
+ t = Math.imul(t ^ t >>> 15, t | 1);
21
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
22
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
23
+ };
24
+ }
25
+ function standardNormal(rng) {
26
+ let u = 0;
27
+ let v = 0;
28
+ while (u === 0) u = rng();
29
+ while (v === 0) v = rng();
30
+ return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
31
+ }
32
+ function gamma(shape, rng) {
33
+ if (shape < 1) {
34
+ const u = rng();
35
+ return gamma(shape + 1, rng) * Math.pow(u, 1 / shape);
36
+ }
37
+ const d = shape - 1 / 3;
38
+ const c = 1 / Math.sqrt(9 * d);
39
+ while (true) {
40
+ let x;
41
+ let v;
42
+ do {
43
+ x = standardNormal(rng);
44
+ v = 1 + c * x;
45
+ } while (v <= 0);
46
+ v = v * v * v;
47
+ const u = rng();
48
+ if (u < 1 - 0.0331 * x * x * x * x) return d * v;
49
+ if (Math.log(u) < x * x / 2 + d * (1 - v + Math.log(v))) return d * v;
50
+ }
51
+ }
52
+ function sampleBeta(alpha, beta, rng) {
53
+ const x = gamma(alpha, rng);
54
+ const y = gamma(beta, rng);
55
+ return x / (x + y);
56
+ }
57
+ function sampleBetaPert(o, m, p, rng) {
58
+ if (o === p) return m;
59
+ const range = p - o;
60
+ const alpha = 1 + 4 * (m - o) / range;
61
+ const beta = 1 + 4 * (p - m) / range;
62
+ return o + sampleBeta(alpha, beta, rng) * range;
63
+ }
64
+ function simulate(resolved, expanded, _predecessors, _successors, topo, terminals, poisoned, opts) {
65
+ const rng = mulberry32(opts.seed);
66
+ const expById = /* @__PURE__ */ new Map();
67
+ for (const e of expanded) expById.set(e.id, e);
68
+ const completionTimes = new Array(opts.trials);
69
+ const criticalityCount = /* @__PURE__ */ new Map();
70
+ for (const id of topo) criticalityCount.set(id, 0);
71
+ const pathCount = /* @__PURE__ */ new Map();
72
+ const pathById = /* @__PURE__ */ new Map();
73
+ const incomingEdges = /* @__PURE__ */ new Map();
74
+ for (const id of topo) incomingEdges.set(id, []);
75
+ for (const e of resolved.edges) incomingEdges.get(e.target)?.push(e);
76
+ const es = /* @__PURE__ */ new Map();
77
+ const ef = /* @__PURE__ */ new Map();
78
+ const predOnLongestPath = /* @__PURE__ */ new Map();
79
+ for (let trial = 0; trial < opts.trials; trial++) {
80
+ for (const id of topo) {
81
+ const exp = expById.get(id);
82
+ let duration;
83
+ if (!exp || poisoned.has(id)) {
84
+ duration = 0;
85
+ } else if (exp.o === exp.p) {
86
+ duration = exp.m;
87
+ } else {
88
+ duration = sampleBetaPert(exp.o, exp.m, exp.p, rng);
89
+ }
90
+ let esLower = 0;
91
+ let efLower = -Infinity;
92
+ let bestPred = null;
93
+ let bestPredVal = -Infinity;
94
+ for (const edge of incomingEdges.get(id) ?? []) {
95
+ const aEs = es.get(edge.source) ?? 0;
96
+ const aEf = ef.get(edge.source) ?? 0;
97
+ const lag = lagDays(edge.lag);
98
+ let candES = -Infinity;
99
+ let candEF = -Infinity;
100
+ switch (edge.type) {
101
+ case "FS":
102
+ candES = aEf + lag;
103
+ break;
104
+ case "SS":
105
+ candES = aEs + lag;
106
+ break;
107
+ case "FF":
108
+ candEF = aEf + lag;
109
+ break;
110
+ case "SF":
111
+ candEF = aEs + lag;
112
+ break;
113
+ }
114
+ if (candES > esLower) esLower = candES;
115
+ if (candEF > efLower) efLower = candEF;
116
+ const contribution = Math.max(candES, candEF - duration);
117
+ if (contribution > bestPredVal) {
118
+ bestPredVal = contribution;
119
+ bestPred = edge.source;
120
+ }
121
+ }
122
+ let esVal = esLower;
123
+ let efVal = esVal + duration;
124
+ if (efLower > efVal) {
125
+ efVal = efLower;
126
+ esVal = efVal - duration;
127
+ }
128
+ es.set(id, esVal);
129
+ ef.set(id, efVal);
130
+ predOnLongestPath.set(id, bestPred);
131
+ }
132
+ let endEf = 0;
133
+ let endId = terminals[0] ?? null;
134
+ for (const id of topo) {
135
+ const e = ef.get(id) ?? 0;
136
+ if (e > endEf) {
137
+ endEf = e;
138
+ endId = id;
139
+ }
140
+ }
141
+ completionTimes[trial] = endEf;
142
+ if (endId !== null) {
143
+ const path = [];
144
+ let cur = endId;
145
+ while (cur !== null) {
146
+ path.unshift(cur);
147
+ criticalityCount.set(cur, (criticalityCount.get(cur) ?? 0) + 1);
148
+ cur = predOnLongestPath.get(cur) ?? null;
149
+ }
150
+ const key = path.join("\0");
151
+ pathCount.set(key, (pathCount.get(key) ?? 0) + 1);
152
+ if (!pathById.has(key)) pathById.set(key, path);
153
+ }
154
+ }
155
+ completionTimes.sort((a, b) => a - b);
156
+ const pct = (frac) => {
157
+ const idx = Math.min(
158
+ completionTimes.length - 1,
159
+ Math.max(0, Math.floor(frac * completionTimes.length))
160
+ );
161
+ return completionTimes[idx] ?? 0;
162
+ };
163
+ const minDurationDays = completionTimes[0] ?? 0;
164
+ const maxDurationDays = completionTimes[completionTimes.length - 1] ?? 0;
165
+ let modalKey = "";
166
+ let modalCount = -1;
167
+ for (const [key, count] of pathCount) {
168
+ if (count > modalCount) {
169
+ modalCount = count;
170
+ modalKey = key;
171
+ } else if (count === modalCount) {
172
+ const a = pathById.get(modalKey) ?? [];
173
+ const b = pathById.get(key) ?? [];
174
+ const va = pathVariance(a, expById);
175
+ const vb = pathVariance(b, expById);
176
+ if (vb > va) {
177
+ modalKey = key;
178
+ } else if (vb === va && key.localeCompare(modalKey) < 0) {
179
+ modalKey = key;
180
+ }
181
+ }
182
+ }
183
+ const criticalityByActivity = {};
184
+ for (const id of topo) {
185
+ criticalityByActivity[id] = (criticalityCount.get(id) ?? 0) / opts.trials;
186
+ }
187
+ return {
188
+ trials: opts.trials,
189
+ seed: opts.seed,
190
+ p50: pct(0.5),
191
+ p80: pct(0.8),
192
+ p95: pct(0.95),
193
+ p16: pct(0.16),
194
+ p84: pct(0.84),
195
+ criticalityByActivity,
196
+ modalCriticalPath: pathById.get(modalKey) ?? [],
197
+ // Analyzer populates this after MC returns by running the
198
+ // deterministic per-activity tornado pass. Empty here so the
199
+ // type contract is satisfied even in scrubber/fast paths that
200
+ // never compute swings.
201
+ tornadoSwings: [],
202
+ minDurationDays,
203
+ maxDurationDays
204
+ };
205
+ }
206
+ function pathVariance(path, expById) {
207
+ let sum = 0;
208
+ for (const id of path) {
209
+ const e = expById.get(id);
210
+ if (!e) continue;
211
+ const sigma = (e.p - e.o) / 6;
212
+ sum += sigma * sigma;
213
+ }
214
+ return sum;
215
+ }
216
+ function buildSimulationContext(resolved) {
217
+ const predecessors = /* @__PURE__ */ new Map();
218
+ const successors = /* @__PURE__ */ new Map();
219
+ for (const r of resolved.activities) {
220
+ predecessors.set(r.activity.id, []);
221
+ successors.set(r.activity.id, []);
222
+ }
223
+ for (const e of resolved.edges) {
224
+ successors.get(e.source)?.push(e.target);
225
+ predecessors.get(e.target)?.push(e.source);
226
+ }
227
+ const inDegree = /* @__PURE__ */ new Map();
228
+ for (const r of resolved.activities) {
229
+ inDegree.set(r.activity.id, predecessors.get(r.activity.id).length);
230
+ }
231
+ const queue = [];
232
+ for (const [id, deg] of inDegree) if (deg === 0) queue.push(id);
233
+ const topo = [];
234
+ while (queue.length > 0) {
235
+ const id = queue.shift();
236
+ topo.push(id);
237
+ for (const next of successors.get(id) ?? []) {
238
+ const deg = (inDegree.get(next) ?? 0) - 1;
239
+ inDegree.set(next, deg);
240
+ if (deg === 0) queue.push(next);
241
+ }
242
+ }
243
+ const terminals = [];
244
+ for (const r of resolved.activities) {
245
+ if ((successors.get(r.activity.id) ?? []).length === 0) {
246
+ terminals.push(r.activity.id);
247
+ }
248
+ }
249
+ const poisoned = /* @__PURE__ */ new Set();
250
+ for (const r of resolved.activities) {
251
+ if (r.activity.duration === null) {
252
+ const stack = [r.activity.id];
253
+ while (stack.length > 0) {
254
+ const id = stack.pop();
255
+ if (poisoned.has(id)) continue;
256
+ poisoned.add(id);
257
+ for (const next of successors.get(id) ?? []) stack.push(next);
258
+ }
259
+ }
260
+ }
261
+ return { predecessors, successors, topo, terminals, poisoned };
262
+ }
263
+ function simulateCanonical(resolved, expanded, opts) {
264
+ const ctx = buildSimulationContext(resolved);
265
+ return simulate(
266
+ resolved,
267
+ expanded,
268
+ ctx.predecessors,
269
+ ctx.successors,
270
+ ctx.topo,
271
+ ctx.terminals,
272
+ ctx.poisoned,
273
+ { trials: opts.trials, seed: opts.seed }
274
+ );
275
+ }
276
+ function simulateFast(resolved, expanded, opts) {
277
+ return simulateCanonical(resolved, expanded, opts);
278
+ }
279
+
280
+ // src/pert/analyzer.ts
281
+ var UNIT_TO_DAYS = {
282
+ min: 1 / (60 * 24),
283
+ h: 1 / 24,
284
+ d: 1,
285
+ bd: 1,
286
+ // PERT has no calendar; bd ≈ d for analytical purposes
287
+ w: 7,
288
+ m: 30,
289
+ q: 90,
290
+ y: 365,
291
+ s: 14
292
+ // fallback when sprint-length isn't authored; the analyzer
293
+ // overrides this from `options.sprintLength` when in sprint mode.
294
+ };
295
+ function unitDays(unit, sprintDaysOverride) {
296
+ if (unit === "s" && sprintDaysOverride !== void 0) {
297
+ return sprintDaysOverride;
298
+ }
299
+ return UNIT_TO_DAYS[unit];
300
+ }
301
+ function toDays(d, sprintDaysOverride) {
302
+ return d.amount * unitDays(d.unit, sprintDaysOverride);
303
+ }
304
+ function fromDays(days, unit, sprintDaysOverride) {
305
+ return days / unitDays(unit, sprintDaysOverride);
306
+ }
307
+ function resolveSprintDays(opts) {
308
+ if (!opts.sprintMode || !opts.sprintLength) return void 0;
309
+ return opts.sprintLength.amount * UNIT_TO_DAYS[opts.sprintLength.unit];
310
+ }
311
+ function expandEstimate(estimate, diagramConfidence, activityConfidence, sprintDays) {
312
+ const o = toDays(estimate.o, sprintDays);
313
+ const m = toDays(estimate.m, sprintDays);
314
+ const p = toDays(estimate.p, sprintDays);
315
+ let oFinal = o;
316
+ let pFinal = p;
317
+ if (estimate.mOnly) {
318
+ const fromActivity = activityConfidence ? resolveConfidence(activityConfidence) : null;
319
+ const factors = fromActivity ?? resolveConfidence(diagramConfidence) ?? CONFIDENCE_TABLE.medium;
320
+ oFinal = m * factors.oFactor;
321
+ pFinal = m * factors.pFactor;
322
+ }
323
+ const mean = (oFinal + 4 * m + pFinal) / 6;
324
+ const sigma = (pFinal - oFinal) / 6;
325
+ return { o: oFinal, m, p: pFinal, mean, sigma };
326
+ }
327
+ function analyzePert(parsed) {
328
+ const diagnostics = [...parsed.diagnostics];
329
+ const error = (line, msg) => {
330
+ diagnostics.push(makeDgmoError(line, msg, "error"));
331
+ };
332
+ const activities = parsed.activities;
333
+ const edges = parsed.edges;
334
+ const warn = (line, msg) => {
335
+ diagnostics.push(makeDgmoError(line, msg, "warning"));
336
+ };
337
+ const sprintDays = resolveSprintDays(parsed.options);
338
+ const successors = /* @__PURE__ */ new Map();
339
+ const predecessors = /* @__PURE__ */ new Map();
340
+ const incomingEdges = /* @__PURE__ */ new Map();
341
+ const outgoingEdges = /* @__PURE__ */ new Map();
342
+ for (const a of activities) {
343
+ successors.set(a.id, []);
344
+ predecessors.set(a.id, []);
345
+ incomingEdges.set(a.id, []);
346
+ outgoingEdges.set(a.id, []);
347
+ }
348
+ for (const e of edges) {
349
+ successors.get(e.source)?.push(e.target);
350
+ predecessors.get(e.target)?.push(e.source);
351
+ incomingEdges.get(e.target)?.push(e);
352
+ outgoingEdges.get(e.source)?.push(e);
353
+ }
354
+ for (const e of edges) {
355
+ if (!e.lag || e.lag.amount >= 0) continue;
356
+ const src = activities.find((a) => a.id === e.source);
357
+ if (!src?.duration) continue;
358
+ const leadDays = -toDays(e.lag, sprintDays);
359
+ const srcDurDays = toDays(src.duration.m, sprintDays);
360
+ if (e.type === "FS" && leadDays > srcDurDays) {
361
+ warn(
362
+ e.lineNumber,
363
+ `Lead (${-e.lag.amount}${e.lag.unit}) exceeds predecessor "${src.name}" duration \u2014 the FS edge implies the successor starts before the predecessor.`
364
+ );
365
+ }
366
+ }
367
+ const topo = [];
368
+ const inDegree = /* @__PURE__ */ new Map();
369
+ for (const a of activities)
370
+ inDegree.set(a.id, predecessors.get(a.id).length);
371
+ const queue = [];
372
+ for (const [id, deg] of inDegree) if (deg === 0) queue.push(id);
373
+ while (queue.length > 0) {
374
+ const id = queue.shift();
375
+ topo.push(id);
376
+ for (const next of successors.get(id) ?? []) {
377
+ const deg = (inDegree.get(next) ?? 0) - 1;
378
+ inDegree.set(next, deg);
379
+ if (deg === 0) queue.push(next);
380
+ }
381
+ }
382
+ if (topo.length < activities.length) {
383
+ const culprit = activities.find((a) => (inDegree.get(a.id) ?? 0) > 0);
384
+ if (culprit) {
385
+ error(culprit.lineNumber, `Cycle detected involving "${culprit.name}".`);
386
+ } else {
387
+ error(0, `Cycle detected in PERT graph.`);
388
+ }
389
+ return emptyResolved(parsed, diagnostics);
390
+ }
391
+ const expandedById = /* @__PURE__ */ new Map();
392
+ for (const a of activities) {
393
+ if (a.duration === null) {
394
+ expandedById.set(a.id, null);
395
+ continue;
396
+ }
397
+ expandedById.set(
398
+ a.id,
399
+ expandEstimate(
400
+ a.duration,
401
+ parsed.options.confidence,
402
+ a.confidence,
403
+ sprintDays
404
+ )
405
+ );
406
+ }
407
+ const poisoned = /* @__PURE__ */ new Set();
408
+ for (const a of activities) {
409
+ if (a.duration === null) {
410
+ const stack = [a.id];
411
+ poisoned.add(a.id);
412
+ while (stack.length > 0) {
413
+ const id = stack.pop();
414
+ for (const next of successors.get(id) ?? []) {
415
+ if (!poisoned.has(next)) {
416
+ poisoned.add(next);
417
+ stack.push(next);
418
+ }
419
+ }
420
+ }
421
+ }
422
+ }
423
+ const es = /* @__PURE__ */ new Map();
424
+ const ef = /* @__PURE__ */ new Map();
425
+ for (const id of topo) {
426
+ if (poisoned.has(id)) {
427
+ es.set(id, null);
428
+ ef.set(id, null);
429
+ continue;
430
+ }
431
+ const expanded = expandedById.get(id);
432
+ const dur = expanded.mean;
433
+ let esLower = 0;
434
+ let efLower = -Infinity;
435
+ for (const edge of incomingEdges.get(id) ?? []) {
436
+ const aEs = es.get(edge.source);
437
+ const aEf = ef.get(edge.source);
438
+ if (aEs == null || aEf == null) continue;
439
+ const lagDays2 = edge.lag ? toDays(edge.lag, sprintDays) : 0;
440
+ switch (edge.type) {
441
+ case "FS":
442
+ esLower = Math.max(esLower, aEf + lagDays2);
443
+ break;
444
+ case "SS":
445
+ esLower = Math.max(esLower, aEs + lagDays2);
446
+ break;
447
+ case "FF":
448
+ efLower = Math.max(efLower, aEf + lagDays2);
449
+ break;
450
+ case "SF":
451
+ efLower = Math.max(efLower, aEs + lagDays2);
452
+ break;
453
+ }
454
+ }
455
+ let esVal = esLower;
456
+ let efVal = esVal + dur;
457
+ if (efLower > efVal) {
458
+ efVal = efLower;
459
+ esVal = efVal - dur;
460
+ }
461
+ es.set(id, esVal);
462
+ ef.set(id, efVal);
463
+ }
464
+ let projectMuDays = null;
465
+ let endId = null;
466
+ let anyTerminalPoisoned = false;
467
+ for (const a of activities) {
468
+ const isTerminal = (successors.get(a.id) ?? []).length === 0;
469
+ if (poisoned.has(a.id)) {
470
+ if (isTerminal) anyTerminalPoisoned = true;
471
+ continue;
472
+ }
473
+ const efVal = ef.get(a.id);
474
+ if (efVal != null && (projectMuDays === null || efVal > projectMuDays)) {
475
+ projectMuDays = efVal;
476
+ endId = a.id;
477
+ }
478
+ }
479
+ if (anyTerminalPoisoned) {
480
+ projectMuDays = null;
481
+ endId = null;
482
+ }
483
+ const ls = /* @__PURE__ */ new Map();
484
+ const lf = /* @__PURE__ */ new Map();
485
+ for (const a of activities) {
486
+ if (poisoned.has(a.id) || projectMuDays === null) {
487
+ ls.set(a.id, null);
488
+ lf.set(a.id, null);
489
+ } else {
490
+ lf.set(a.id, projectMuDays);
491
+ const exp = expandedById.get(a.id);
492
+ ls.set(a.id, projectMuDays - exp.mean);
493
+ }
494
+ }
495
+ for (let i = topo.length - 1; i >= 0; i--) {
496
+ const id = topo[i];
497
+ if (poisoned.has(id) || projectMuDays === null) continue;
498
+ const out = outgoingEdges.get(id) ?? [];
499
+ if (out.length === 0) continue;
500
+ const exp = expandedById.get(id);
501
+ const dur = exp.mean;
502
+ let lfUpper = Infinity;
503
+ let lsUpper = Infinity;
504
+ for (const edge of out) {
505
+ const bLs = ls.get(edge.target);
506
+ const bLf = lf.get(edge.target);
507
+ if (bLs == null || bLf == null) continue;
508
+ const lagDays2 = edge.lag ? toDays(edge.lag, sprintDays) : 0;
509
+ switch (edge.type) {
510
+ case "FS":
511
+ lfUpper = Math.min(lfUpper, bLs - lagDays2);
512
+ break;
513
+ case "SS":
514
+ lsUpper = Math.min(lsUpper, bLs - lagDays2);
515
+ break;
516
+ case "FF":
517
+ lfUpper = Math.min(lfUpper, bLf - lagDays2);
518
+ break;
519
+ case "SF":
520
+ lsUpper = Math.min(lsUpper, bLf - lagDays2);
521
+ break;
522
+ }
523
+ }
524
+ let lfVal = lfUpper;
525
+ let lsVal = lfVal - dur;
526
+ if (lsUpper < lsVal) {
527
+ lsVal = lsUpper;
528
+ lfVal = lsVal + dur;
529
+ }
530
+ lf.set(id, lfVal);
531
+ ls.set(id, lsVal);
532
+ }
533
+ const criticalPath = [];
534
+ if (endId !== null) {
535
+ let cursor = endId;
536
+ while (cursor !== null) {
537
+ criticalPath.unshift(cursor);
538
+ const preds = predecessors.get(cursor) ?? [];
539
+ if (preds.length === 0) break;
540
+ let bestPred = null;
541
+ let bestEf = -Infinity;
542
+ for (const pid of preds) {
543
+ const efp = ef.get(pid);
544
+ if (efp != null && efp > bestEf) {
545
+ bestEf = efp;
546
+ bestPred = pid;
547
+ }
548
+ }
549
+ cursor = bestPred;
550
+ }
551
+ }
552
+ const criticalSet = new Set(criticalPath);
553
+ let projectSigmaDays = null;
554
+ if (projectMuDays !== null && criticalPath.length > 0) {
555
+ let varSum = 0;
556
+ for (const id of criticalPath) {
557
+ const exp = expandedById.get(id);
558
+ if (exp) varSum += exp.sigma * exp.sigma;
559
+ }
560
+ projectSigmaDays = Math.sqrt(varSum);
561
+ }
562
+ const unit = parsed.options.timeUnit;
563
+ const resolvedActivities = activities.map((a) => {
564
+ const exp = expandedById.get(a.id);
565
+ const slackDays = ls.get(a.id) !== null && es.get(a.id) !== null ? ls.get(a.id) - es.get(a.id) : null;
566
+ return {
567
+ activity: a,
568
+ es: nullableToUnit(es.get(a.id) ?? null, unit, sprintDays),
569
+ ef: nullableToUnit(ef.get(a.id) ?? null, unit, sprintDays),
570
+ ls: nullableToUnit(ls.get(a.id) ?? null, unit, sprintDays),
571
+ lf: nullableToUnit(lf.get(a.id) ?? null, unit, sprintDays),
572
+ slack: nullableToUnit(slackDays, unit, sprintDays),
573
+ isCriticalPath: criticalSet.has(a.id),
574
+ mu: exp ? fromDays(exp.mean, unit, sprintDays) : null,
575
+ sigma: exp ? fromDays(exp.sigma, unit, sprintDays) : null,
576
+ criticality: null,
577
+ isAuthored: a.duration !== null && !a.duration.mOnly && !a.isMilestone
578
+ };
579
+ });
580
+ let monteCarloResult = null;
581
+ const dataDrivenMC = activities.some((a) => hasDuration(a));
582
+ const trialsClamped = dataDrivenMC && parsed.options.trials < 100;
583
+ let mode = dataDrivenMC && !trialsClamped ? "monte-carlo" : "analytical";
584
+ if (mode === "monte-carlo") {
585
+ const allTerminalsPoisoned = activities.filter((a) => (successors.get(a.id) ?? []).length === 0).every((a) => poisoned.has(a.id));
586
+ if (allTerminalsPoisoned && activities.length > 0) {
587
+ mode = "analytical";
588
+ } else {
589
+ const expandedArr = [];
590
+ for (const a of activities) {
591
+ const exp = expandedById.get(a.id);
592
+ if (exp) expandedArr.push({ id: a.id, o: exp.o, m: exp.m, p: exp.p });
593
+ }
594
+ const prelim = {
595
+ options: parsed.options,
596
+ activities: resolvedActivities,
597
+ edges: edges.slice(),
598
+ groups: [],
599
+ tagGroups: parsed.tagGroups.slice(),
600
+ mode: "monte-carlo",
601
+ summaryRows: null,
602
+ projectSubtitle: null,
603
+ projectMu: null,
604
+ projectSigma: null,
605
+ criticalPath,
606
+ projectStart: null,
607
+ monteCarloResult: null,
608
+ expandedActivities: expandedArr,
609
+ diagnostics: [],
610
+ error: null
611
+ };
612
+ monteCarloResult = simulateCanonical(prelim, expandedArr, {
613
+ trials: parsed.options.trials,
614
+ seed: parsed.options.seed
615
+ });
616
+ for (const ra of resolvedActivities) {
617
+ if (poisoned.has(ra.activity.id)) continue;
618
+ const c = monteCarloResult.criticalityByActivity[ra.activity.id];
619
+ ra.criticality = typeof c === "number" ? c : null;
620
+ }
621
+ monteCarloResult.tornadoSwings = computeTornadoSwings(
622
+ activities,
623
+ topo,
624
+ incomingEdges,
625
+ expandedById,
626
+ poisoned,
627
+ monteCarloResult.criticalityByActivity,
628
+ sprintDays
629
+ );
630
+ }
631
+ }
632
+ const rollupSet = monteCarloResult && monteCarloResult.modalCriticalPath.length > 0 ? new Set(monteCarloResult.modalCriticalPath) : criticalSet;
633
+ const resolvedById = new Map(
634
+ resolvedActivities.map((r) => [r.activity.id, r])
635
+ );
636
+ const resolvedGroups = parsed.groups.map(
637
+ (g) => rollupGroup(g, expandedById, resolvedById, rollupSet, unit, sprintDays)
638
+ );
639
+ const publicExpanded = [];
640
+ for (const a of activities) {
641
+ const exp = expandedById.get(a.id);
642
+ if (exp) publicExpanded.push({ id: a.id, o: exp.o, m: exp.m, p: exp.p });
643
+ }
644
+ const projectMuOut = projectMuDays === null ? null : fromDays(projectMuDays, unit, sprintDays);
645
+ const projectSigmaOut = projectSigmaDays === null ? null : fromDays(projectSigmaDays, unit, sprintDays);
646
+ let projectStart = null;
647
+ const anchor = parsed.options.anchor;
648
+ if (anchor !== null) {
649
+ if (anchor.kind === "forward") {
650
+ projectStart = anchor.date;
651
+ } else if (anchor.kind === "backward" && projectMuDays !== null) {
652
+ projectStart = addCalendarDays(anchor.date, -projectMuDays);
653
+ }
654
+ }
655
+ const summaryRows = buildSummary({
656
+ mode,
657
+ projectMu: projectMuOut,
658
+ projectSigma: projectSigmaOut,
659
+ unit,
660
+ parsedActivities: activities,
661
+ monteCarloResult,
662
+ trialsClamped,
663
+ anchor: parsed.options.anchor,
664
+ today: parsed.options.today,
665
+ ...sprintDays !== void 0 && { sprintDays }
666
+ });
667
+ const projectSubtitle = buildProjectSubtitle({
668
+ projectMu: projectMuOut,
669
+ projectSigma: projectSigmaOut,
670
+ unit,
671
+ anchor: parsed.options.anchor
672
+ });
673
+ return {
674
+ options: parsed.options,
675
+ activities: resolvedActivities,
676
+ edges: edges.slice(),
677
+ groups: resolvedGroups,
678
+ tagGroups: parsed.tagGroups.slice(),
679
+ mode,
680
+ summaryRows,
681
+ projectSubtitle,
682
+ projectMu: projectMuOut,
683
+ projectSigma: projectSigmaOut,
684
+ criticalPath,
685
+ projectStart,
686
+ monteCarloResult,
687
+ expandedActivities: publicExpanded,
688
+ diagnostics,
689
+ error: parsed.error ?? firstFatal(diagnostics)
690
+ };
691
+ }
692
+ function hasDuration(a) {
693
+ if (a.duration === null || a.isMilestone) return false;
694
+ if (a.duration.mOnly) return a.duration.m.amount > 0;
695
+ return true;
696
+ }
697
+ function computeProjectEndDays(activities, topo, incomingEdges, durations, poisoned, sprintDays) {
698
+ const es = /* @__PURE__ */ new Map();
699
+ const ef = /* @__PURE__ */ new Map();
700
+ for (const id of topo) {
701
+ const dur = poisoned.has(id) ? 0 : durations.get(id) ?? 0;
702
+ let esLower = 0;
703
+ let efLower = -Infinity;
704
+ for (const edge of incomingEdges.get(id) ?? []) {
705
+ const aEs = es.get(edge.source) ?? 0;
706
+ const aEf = ef.get(edge.source) ?? 0;
707
+ const lag = edge.lag ? toDays(edge.lag, sprintDays) : 0;
708
+ switch (edge.type) {
709
+ case "FS":
710
+ esLower = Math.max(esLower, aEf + lag);
711
+ break;
712
+ case "SS":
713
+ esLower = Math.max(esLower, aEs + lag);
714
+ break;
715
+ case "FF":
716
+ efLower = Math.max(efLower, aEf + lag);
717
+ break;
718
+ case "SF":
719
+ efLower = Math.max(efLower, aEs + lag);
720
+ break;
721
+ }
722
+ }
723
+ let esVal = esLower;
724
+ let efVal = esVal + dur;
725
+ if (efLower > efVal) {
726
+ efVal = efLower;
727
+ esVal = efVal - dur;
728
+ }
729
+ es.set(id, esVal);
730
+ ef.set(id, efVal);
731
+ }
732
+ let projectEnd = 0;
733
+ for (const a of activities) {
734
+ const e = ef.get(a.id) ?? 0;
735
+ if (e > projectEnd) projectEnd = e;
736
+ }
737
+ return projectEnd;
738
+ }
739
+ function computeTornadoSwings(activities, topo, incomingEdges, expandedById, poisoned, criticalityByActivity, sprintDays) {
740
+ const meansById = /* @__PURE__ */ new Map();
741
+ for (const a of activities) {
742
+ if (poisoned.has(a.id)) continue;
743
+ const exp = expandedById.get(a.id);
744
+ if (exp) meansById.set(a.id, exp.mean);
745
+ }
746
+ const baseline = computeProjectEndDays(
747
+ activities,
748
+ topo,
749
+ incomingEdges,
750
+ meansById,
751
+ poisoned,
752
+ sprintDays
753
+ );
754
+ const swings = [];
755
+ for (const a of activities) {
756
+ if (a.isMilestone || a.duration === null || poisoned.has(a.id)) continue;
757
+ const exp = expandedById.get(a.id);
758
+ if (!exp) continue;
759
+ if (exp.o === exp.m && exp.p === exp.m) continue;
760
+ const lowMap = new Map(meansById);
761
+ lowMap.set(a.id, exp.o);
762
+ const lowEnd = computeProjectEndDays(
763
+ activities,
764
+ topo,
765
+ incomingEdges,
766
+ lowMap,
767
+ poisoned,
768
+ sprintDays
769
+ );
770
+ const highMap = new Map(meansById);
771
+ highMap.set(a.id, exp.p);
772
+ const highEnd = computeProjectEndDays(
773
+ activities,
774
+ topo,
775
+ incomingEdges,
776
+ highMap,
777
+ poisoned,
778
+ sprintDays
779
+ );
780
+ const lowSwing = Math.max(0, baseline - lowEnd);
781
+ const highSwing = Math.max(0, highEnd - baseline);
782
+ if (lowSwing === 0 && highSwing === 0) continue;
783
+ const c = criticalityByActivity[a.id];
784
+ swings.push({
785
+ id: a.id,
786
+ name: a.name,
787
+ lowSwing,
788
+ highSwing,
789
+ criticality: typeof c === "number" ? c : null
790
+ });
791
+ }
792
+ swings.sort((a, b) => b.lowSwing + b.highSwing - (a.lowSwing + a.highSwing));
793
+ return swings;
794
+ }
795
+ function nullableToUnit(value, unit, sprintDays) {
796
+ return value === null ? null : fromDays(value, unit, sprintDays);
797
+ }
798
+ function firstFatal(diagnostics) {
799
+ const f = diagnostics.find((d) => d.severity === "error");
800
+ return f ? f.message : null;
801
+ }
802
+ function rollupGroup(group, expandedById, resolvedById, criticalSet, unit, sprintDays) {
803
+ let muDays = 0;
804
+ let varDays = 0;
805
+ let usable = false;
806
+ for (const id of group.activityIds) {
807
+ if (!criticalSet.has(id)) continue;
808
+ const exp = expandedById.get(id);
809
+ if (!exp) continue;
810
+ muDays += exp.mean;
811
+ varDays += exp.sigma * exp.sigma;
812
+ usable = true;
813
+ }
814
+ let es = null;
815
+ let ef = null;
816
+ let ls = null;
817
+ let lf = null;
818
+ let criticality = null;
819
+ for (const id of group.activityIds) {
820
+ const r = resolvedById.get(id);
821
+ if (!r) continue;
822
+ if (r.es !== null) es = es === null ? r.es : Math.min(es, r.es);
823
+ if (r.ef !== null) ef = ef === null ? r.ef : Math.max(ef, r.ef);
824
+ if (r.ls !== null) ls = ls === null ? r.ls : Math.min(ls, r.ls);
825
+ if (r.lf !== null) lf = lf === null ? r.lf : Math.max(lf, r.lf);
826
+ if (r.criticality !== null) {
827
+ criticality = criticality === null ? r.criticality : Math.max(criticality, r.criticality);
828
+ }
829
+ }
830
+ const slack = ls !== null && es !== null ? ls - es : null;
831
+ return {
832
+ group,
833
+ rolledMu: usable ? fromDays(muDays, unit, sprintDays) : null,
834
+ rolledSigma: usable ? fromDays(Math.sqrt(varDays), unit, sprintDays) : null,
835
+ entries: [],
836
+ exits: [],
837
+ es,
838
+ ef,
839
+ ls,
840
+ lf,
841
+ slack,
842
+ criticality
843
+ };
844
+ }
845
+ function roundConservative(durationDays, _mode) {
846
+ return Math.ceil(durationDays);
847
+ }
848
+ function buildSummary(input) {
849
+ const {
850
+ mode,
851
+ projectMu,
852
+ projectSigma,
853
+ unit,
854
+ parsedActivities,
855
+ monteCarloResult,
856
+ trialsClamped,
857
+ sprintDays,
858
+ today
859
+ } = input;
860
+ const anchor = input.anchor ?? null;
861
+ if (parsedActivities.length === 0) return null;
862
+ if (projectMu === null) {
863
+ if (anchor?.kind === "backward") {
864
+ return [
865
+ { text: "Expected duration: ?", level: 0 },
866
+ { text: "P50 latest-safe start: ?", level: 0 },
867
+ { text: "P80 latest-safe start: ?", level: 0 },
868
+ { text: "P95 latest-safe start: ?", level: 0 }
869
+ ];
870
+ }
871
+ const tbdCount = parsedActivities.filter(
872
+ (a) => a.duration === null && !a.isMilestone
873
+ ).length;
874
+ return [
875
+ {
876
+ text: `Expected duration unknown \u2014 ${tbdCount} ${tbdCount === 1 ? "activity has" : "activities have"} no estimate.`,
877
+ level: 0
878
+ }
879
+ ];
880
+ }
881
+ const rows = [];
882
+ const mc = mode === "monte-carlo" && monteCarloResult !== null;
883
+ const sigmaPositive = projectSigma !== null && projectSigma > 0;
884
+ const showMcDetail = mc && sigmaPositive;
885
+ const sigmaParen = showMcDetail ? ` (\xB1 ${roundForCaption(projectSigma)} ${pluralizeUnit(projectSigma, unit)})` : "";
886
+ if (anchor?.kind === "forward") {
887
+ const projectMuDays = projectMu * unitToDays(unit);
888
+ rows.push({
889
+ text: `Expected finish: ${addCalendarDays(anchor.date, projectMuDays)}${sigmaParen}.`,
890
+ level: 0
891
+ });
892
+ } else if (anchor?.kind === "backward") {
893
+ const projectMuDays = projectMu * unitToDays(unit);
894
+ rows.push({
895
+ text: `Expected start: ${addCalendarDays(anchor.date, -projectMuDays)}${sigmaParen}.`,
896
+ level: 0
897
+ });
898
+ } else {
899
+ const muStr = `${roundForCaption(projectMu)} ${pluralizeUnit(projectMu, unit)}`;
900
+ rows.push({
901
+ text: `Expected duration: ${muStr}${sigmaParen}.`,
902
+ level: 0
903
+ });
904
+ }
905
+ if (showMcDetail) {
906
+ const percentiles = [
907
+ { pct: 50, days: monteCarloResult.p50 },
908
+ { pct: 80, days: monteCarloResult.p80 },
909
+ { pct: 95, days: monteCarloResult.p95 }
910
+ ];
911
+ if (anchor?.kind === "forward") {
912
+ for (const { pct, days } of percentiles) {
913
+ const offsetDays = roundConservative(days, "forward");
914
+ const date = addCalendarDays(anchor.date, offsetDays);
915
+ rows.push({ text: `P${pct} finish: ${date}.`, level: 1 });
916
+ }
917
+ } else if (anchor?.kind === "backward") {
918
+ for (const { pct, days } of percentiles) {
919
+ const offsetDays = roundConservative(days, "backward");
920
+ const date = addCalendarDays(anchor.date, -offsetDays);
921
+ const isPast = today.length > 0 && date < today;
922
+ const suffix = isPast ? " (latest-safe start has passed)" : "";
923
+ const row = {
924
+ text: `P${pct} latest-safe start: ${date}${suffix}`,
925
+ level: 1
926
+ };
927
+ if (isPast) row.isPast = true;
928
+ rows.push(row);
929
+ }
930
+ } else {
931
+ for (const { pct, days } of percentiles) {
932
+ const v = fromDays(days, unit, sprintDays);
933
+ rows.push({ text: `P${pct}: ${formatPercentile(v, unit)}.`, level: 1 });
934
+ }
935
+ }
936
+ }
937
+ if (mc && projectSigma === 0) {
938
+ const filtered = rows.filter(
939
+ (r) => r.text.startsWith("Expected ")
940
+ );
941
+ filtered.push({
942
+ text: "(No variance in estimates \u2014 all activities have O = M = P.)",
943
+ level: 0
944
+ });
945
+ return filtered;
946
+ }
947
+ if (trialsClamped) {
948
+ rows.push({
949
+ text: "Insufficient trials configured (`trials` < 100) \u2014 falling back to deterministic analysis.",
950
+ level: 0
951
+ });
952
+ }
953
+ return rows;
954
+ }
955
+ function buildProjectSubtitle(input) {
956
+ const anchor = input.anchor ?? null;
957
+ const { projectMu, projectSigma, unit } = input;
958
+ const sigmaPositive = projectSigma !== null && projectSigma > 0;
959
+ const sigmaParen = sigmaPositive ? ` (\xB1 ${roundForCaption(projectSigma)})` : "";
960
+ if (projectMu === null) {
961
+ if (anchor?.kind === "forward") {
962
+ return `Expected finish: ? \xB7 \u2248 ? ${pluralizeUnit(2, unit)} of work`;
963
+ }
964
+ if (anchor?.kind === "backward") {
965
+ return `Expected start: ? \xB7 \u2248 ? ${pluralizeUnit(2, unit)} lead time`;
966
+ }
967
+ return `\u2248 ? ${pluralizeUnit(2, unit)}`;
968
+ }
969
+ const muStr = `${roundForCaption(projectMu)} ${pluralizeUnit(projectMu, unit)}`;
970
+ if (anchor?.kind === "forward") {
971
+ const projectMuDays = projectMu * unitToDays(unit);
972
+ return `Expected finish: ${addCalendarDays(anchor.date, projectMuDays)} \xB7 \u2248 ${muStr} of work${sigmaParen}`;
973
+ }
974
+ if (anchor?.kind === "backward") {
975
+ const projectMuDays = projectMu * unitToDays(unit);
976
+ return `Expected start: ${addCalendarDays(anchor.date, -projectMuDays)} \xB7 \u2248 ${muStr} lead time${sigmaParen}`;
977
+ }
978
+ return `\u2248 ${muStr}${sigmaParen}`;
979
+ }
980
+ function roundForCaption(n) {
981
+ let rounded;
982
+ const abs = Math.abs(n);
983
+ if (abs < 10) rounded = Math.round(n * 100) / 100;
984
+ else if (abs < 100) rounded = Math.round(n * 10) / 10;
985
+ else rounded = Math.round(n);
986
+ const str = rounded.toString();
987
+ return str.includes(".") ? str.replace(/\.?0+$/, "") : str;
988
+ }
989
+ var UNIT_WORDS = {
990
+ min: ["minute", "minutes"],
991
+ h: ["hour", "hours"],
992
+ d: ["day", "days"],
993
+ bd: ["business day", "business days"],
994
+ w: ["week", "weeks"],
995
+ m: ["month", "months"],
996
+ q: ["quarter", "quarters"],
997
+ y: ["year", "years"],
998
+ s: ["sprint", "sprints"]
999
+ };
1000
+ function pluralizeUnit(value, unit) {
1001
+ const [singular, plural] = UNIT_WORDS[unit];
1002
+ return roundForCaption(value) === "1" ? singular : plural;
1003
+ }
1004
+ function formatPercentile(value, unit) {
1005
+ return `${roundForCaption(value)} ${pluralizeUnit(value, unit)}`;
1006
+ }
1007
+ function emptyResolved(parsed, diagnostics) {
1008
+ return {
1009
+ options: parsed.options,
1010
+ activities: parsed.activities.map((a) => ({
1011
+ activity: a,
1012
+ es: null,
1013
+ ef: null,
1014
+ ls: null,
1015
+ lf: null,
1016
+ slack: null,
1017
+ isCriticalPath: false,
1018
+ mu: null,
1019
+ sigma: null,
1020
+ criticality: null,
1021
+ isAuthored: false
1022
+ })),
1023
+ edges: parsed.edges.slice(),
1024
+ groups: parsed.groups.map((g) => ({
1025
+ group: g,
1026
+ rolledMu: null,
1027
+ rolledSigma: null,
1028
+ entries: [],
1029
+ exits: [],
1030
+ es: null,
1031
+ ef: null,
1032
+ ls: null,
1033
+ lf: null,
1034
+ slack: null,
1035
+ criticality: null
1036
+ })),
1037
+ tagGroups: parsed.tagGroups.slice(),
1038
+ mode: "analytical",
1039
+ summaryRows: null,
1040
+ projectSubtitle: null,
1041
+ projectMu: null,
1042
+ projectSigma: null,
1043
+ criticalPath: [],
1044
+ projectStart: null,
1045
+ monteCarloResult: null,
1046
+ expandedActivities: [],
1047
+ diagnostics,
1048
+ error: firstFatal(diagnostics) ?? parsed.error
1049
+ };
1050
+ }
1051
+
1052
+ export {
1053
+ mulberry32,
1054
+ sampleBetaPert,
1055
+ buildSimulationContext,
1056
+ simulateCanonical,
1057
+ simulateFast,
1058
+ analyzePert,
1059
+ buildSummary,
1060
+ buildProjectSubtitle
1061
+ };
1062
+ //# sourceMappingURL=chunk-NX4EQS3U.js.map