@axiapps/bridge-metrics 0.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 (86) hide show
  1. package/dist/aggregationTypes.cjs +18 -0
  2. package/dist/aggregationTypes.d.cts +21 -0
  3. package/dist/aggregationTypes.d.ts +21 -0
  4. package/dist/aggregationTypes.js +1 -0
  5. package/dist/boonGeneration.cjs +308 -0
  6. package/dist/boonGeneration.d.cts +65 -0
  7. package/dist/boonGeneration.d.ts +65 -0
  8. package/dist/boonGeneration.js +16 -0
  9. package/dist/chunk-42DVJJLC.js +106 -0
  10. package/dist/chunk-4F55Q6K2.js +0 -0
  11. package/dist/chunk-ELKDB763.js +349 -0
  12. package/dist/chunk-FU74LJEM.js +77 -0
  13. package/dist/chunk-J3YCFY3C.js +37 -0
  14. package/dist/chunk-JP2ZL44R.js +18 -0
  15. package/dist/chunk-K2SRAMGC.js +0 -0
  16. package/dist/chunk-KRHODGVU.js +48 -0
  17. package/dist/chunk-LIGIXSSA.js +1383 -0
  18. package/dist/chunk-M2WR3JBQ.js +0 -0
  19. package/dist/chunk-PMVLNDZZ.js +279 -0
  20. package/dist/chunk-R5EJF5AW.js +147 -0
  21. package/dist/chunk-RGXSI3AI.js +298 -0
  22. package/dist/chunk-UFJJ6WLD.js +197 -0
  23. package/dist/chunk-WW5XFXGC.js +234 -0
  24. package/dist/chunk-ZFZS7JFU.js +10 -0
  25. package/dist/combatMetrics.cjs +251 -0
  26. package/dist/combatMetrics.d.cts +26 -0
  27. package/dist/combatMetrics.d.ts +26 -0
  28. package/dist/combatMetrics.js +21 -0
  29. package/dist/computePlayerAggregation.cjs +2042 -0
  30. package/dist/computePlayerAggregation.d.cts +249 -0
  31. package/dist/computePlayerAggregation.d.ts +249 -0
  32. package/dist/computePlayerAggregation.js +30 -0
  33. package/dist/conditionsMetrics.cjs +328 -0
  34. package/dist/conditionsMetrics.d.cts +67 -0
  35. package/dist/conditionsMetrics.d.ts +67 -0
  36. package/dist/conditionsMetrics.js +18 -0
  37. package/dist/constants.cjs +36 -0
  38. package/dist/constants.d.cts +18 -0
  39. package/dist/constants.d.ts +18 -0
  40. package/dist/constants.js +10 -0
  41. package/dist/dashboardMetrics.cjs +226 -0
  42. package/dist/dashboardMetrics.d.cts +29 -0
  43. package/dist/dashboardMetrics.d.ts +29 -0
  44. package/dist/dashboardMetrics.js +42 -0
  45. package/dist/dpsReportTypes.cjs +18 -0
  46. package/dist/dpsReportTypes.d.cts +294 -0
  47. package/dist/dpsReportTypes.d.ts +294 -0
  48. package/dist/dpsReportTypes.js +1 -0
  49. package/dist/index.cjs +2902 -0
  50. package/dist/index.d.cts +13 -0
  51. package/dist/index.d.ts +13 -0
  52. package/dist/index.js +152 -0
  53. package/dist/metrics-methods.json +38 -0
  54. package/dist/metricsSettings.cjs +75 -0
  55. package/dist/metricsSettings.d.cts +23 -0
  56. package/dist/metricsSettings.d.ts +23 -0
  57. package/dist/metricsSettings.js +8 -0
  58. package/dist/professionUtils.cjs +265 -0
  59. package/dist/professionUtils.d.cts +10 -0
  60. package/dist/professionUtils.d.ts +10 -0
  61. package/dist/professionUtils.js +20 -0
  62. package/dist/reportMetrics.cjs +224 -0
  63. package/dist/reportMetrics.d.cts +85 -0
  64. package/dist/reportMetrics.d.ts +85 -0
  65. package/dist/reportMetrics.js +12 -0
  66. package/dist/resUtility.cjs +52 -0
  67. package/dist/resUtility.d.cts +5 -0
  68. package/dist/resUtility.d.ts +5 -0
  69. package/dist/resUtility.js +8 -0
  70. package/dist/roles.cjs +18 -0
  71. package/dist/roles.d.cts +17 -0
  72. package/dist/roles.d.ts +17 -0
  73. package/dist/roles.js +1 -0
  74. package/dist/rollup.cjs +378 -0
  75. package/dist/rollup.d.cts +103 -0
  76. package/dist/rollup.d.ts +103 -0
  77. package/dist/rollup.js +16 -0
  78. package/dist/statsMetrics.cjs +153 -0
  79. package/dist/statsMetrics.d.cts +39 -0
  80. package/dist/statsMetrics.d.ts +39 -0
  81. package/dist/statsMetrics.js +22 -0
  82. package/dist/timestampUtils.cjs +63 -0
  83. package/dist/timestampUtils.d.cts +12 -0
  84. package/dist/timestampUtils.d.ts +12 -0
  85. package/dist/timestampUtils.js +9 -0
  86. package/package.json +44 -0
@@ -0,0 +1,298 @@
1
+ // src/conditionsMetrics.ts
2
+ var NON_DAMAGING_CONDITIONS = /* @__PURE__ */ new Set([
3
+ "Vulnerability",
4
+ "Weakness",
5
+ "Blind",
6
+ "Cripple",
7
+ "Chill",
8
+ "Immobilize",
9
+ "Slow",
10
+ "Fear",
11
+ "Taunt"
12
+ ]);
13
+ var CONDITION_NAME_MAP = /* @__PURE__ */ new Map([
14
+ ["bleeding", "Bleeding"],
15
+ ["burning", "Burning"],
16
+ ["confusion", "Confusion"],
17
+ ["poison", "Poison"],
18
+ ["torment", "Torment"],
19
+ ["vulnerability", "Vulnerability"],
20
+ ["weakness", "Weakness"],
21
+ ["weakened", "Weakness"],
22
+ ["blind", "Blind"],
23
+ ["blinded", "Blind"],
24
+ ["blinding", "Blind"],
25
+ ["cripple", "Cripple"],
26
+ ["crippled", "Cripple"],
27
+ ["chill", "Chill"],
28
+ ["chilled", "Chill"],
29
+ ["immob", "Immobilize"],
30
+ ["immobile", "Immobilize"],
31
+ ["immobilized", "Immobilize"],
32
+ ["slow", "Slow"],
33
+ ["slowed", "Slow"],
34
+ ["fear", "Fear"],
35
+ ["feared", "Fear"],
36
+ ["taunt", "Taunt"],
37
+ ["taunted", "Taunt"]
38
+ ]);
39
+ var DEFAULT_CONDITION_ICONS = {
40
+ Blind: "https://render.guildwars2.com/file/09770136BB76FD0DBE1CC4267DEED54774CB20F6/102837.png",
41
+ Chill: "https://render.guildwars2.com/file/28C4EC547A3516AF0242E826772DA43A5EAC3DF3/102839.png",
42
+ Cripple: "https://render.guildwars2.com/file/070325E519C178D502A8160523766070D30C0C19/102838.png",
43
+ Fear: "https://render.guildwars2.com/file/30307A6E766D74B6EB09EDA12A4A2DE50E4D76F4/102869.png",
44
+ Immobilize: "https://render.guildwars2.com/file/397A613651BFCA2832B6469CE34735580A2C120E/102844.png",
45
+ Slow: "https://render.guildwars2.com/file/F60D1EF5271D7B9319610855676D320CD25F01C6/961397.png",
46
+ Taunt: "https://render.guildwars2.com/file/02EED459AD65FAF7DF32A260E479C625070841B9/1228472.png",
47
+ Vulnerability: "https://render.guildwars2.com/file/3A394C1A0A3257EB27A44842DDEEF0DF000E1241/102850.png",
48
+ Weakness: "https://render.guildwars2.com/file/6CB0E64AF9AA292E332A38C1770CE577E2CDE0E8/102853.png"
49
+ };
50
+ var getDefaultConditionIcon = (name) => {
51
+ if (!name) return void 0;
52
+ return DEFAULT_CONDITION_ICONS[name];
53
+ };
54
+ var resolveBuffMetaById = (buffMap, id) => {
55
+ if (!buffMap || id === void 0 || id === null) return void 0;
56
+ const numericId = Number(id);
57
+ if (Number.isFinite(numericId)) {
58
+ return buffMap[`b${numericId}`] || buffMap[String(numericId)];
59
+ }
60
+ return buffMap[String(id)];
61
+ };
62
+ var getConditionName = (name) => {
63
+ if (!name) return null;
64
+ const cleaned = name.trim().toLowerCase();
65
+ const directMatch = CONDITION_NAME_MAP.get(cleaned);
66
+ if (directMatch) return directMatch;
67
+ const tokens = cleaned.split(/[^a-z]+/).filter(Boolean);
68
+ for (const token of tokens) {
69
+ const match = CONDITION_NAME_MAP.get(token);
70
+ if (match) return match;
71
+ }
72
+ return null;
73
+ };
74
+ var normalizeConditionLabel = (name) => getConditionName(name);
75
+ var buildConditionIconMap = (buffMap) => {
76
+ const map = /* @__PURE__ */ new Map();
77
+ if (!buffMap) return map;
78
+ Object.values(buffMap).forEach((meta) => {
79
+ if (!meta?.icon || !meta?.name) return;
80
+ const normalized = getConditionName(meta.name);
81
+ if (!normalized) return;
82
+ if (!map.has(normalized)) map.set(normalized, meta.icon);
83
+ });
84
+ Object.entries(DEFAULT_CONDITION_ICONS).forEach(([name, icon]) => {
85
+ if (!map.has(name)) map.set(name, icon);
86
+ });
87
+ return map;
88
+ };
89
+ var resolveConditionNameFromEntry = (skillName, id, buffMap) => {
90
+ if (id && buffMap) {
91
+ const buffName = resolveBuffMetaById(buffMap, id)?.name;
92
+ const resolved = getConditionName(buffName);
93
+ if (resolved) return resolved;
94
+ }
95
+ if (!skillName) return null;
96
+ return getConditionName(skillName);
97
+ };
98
+ var defaultGetPlayerKey = (player) => {
99
+ const account = player?.account || "Unknown";
100
+ if (account && account !== "Unknown") return account;
101
+ const name = player?.name || "Unknown";
102
+ return name || null;
103
+ };
104
+ var countAppliedFromStates = (states) => {
105
+ if (!states || states.length === 0) return 0;
106
+ let applied = 0;
107
+ let prev = null;
108
+ states.forEach((entry) => {
109
+ const value = Number(entry[1] ?? 0);
110
+ if (!Number.isFinite(value)) return;
111
+ if (prev === null) {
112
+ prev = value;
113
+ return;
114
+ }
115
+ if (value > prev) {
116
+ applied += value - prev;
117
+ }
118
+ prev = value;
119
+ });
120
+ return applied;
121
+ };
122
+ var computeUptimeFromStates = (states) => {
123
+ if (!states || states.length === 0) return 0;
124
+ let uptimeMs = 0;
125
+ let buffOn = 0;
126
+ let firstTime = 0;
127
+ for (const [time, value] of states) {
128
+ if (time === 0) continue;
129
+ if (value >= 1 && buffOn === 0) {
130
+ buffOn = value;
131
+ firstTime = time;
132
+ } else if (value === 0 && buffOn > 0) {
133
+ uptimeMs += time - firstTime;
134
+ buffOn = 0;
135
+ }
136
+ }
137
+ return uptimeMs;
138
+ };
139
+ var countActiveStateEntries = (states) => {
140
+ if (!states || states.length === 0) return 0;
141
+ let count = 0;
142
+ states.forEach((entry) => {
143
+ const time = Number(entry[0] ?? 0);
144
+ const value = Number(entry[1] ?? 0);
145
+ if (!Number.isFinite(value)) return;
146
+ if (time === 0) return;
147
+ if (value > 0) count += 1;
148
+ });
149
+ return count;
150
+ };
151
+ var computeOutgoingConditions = (payload) => {
152
+ const { players, targets, skillMap, buffMap } = payload;
153
+ const getPlayerKey = payload.getPlayerKey || defaultGetPlayerKey;
154
+ const conditionIconMap = buildConditionIconMap(buffMap);
155
+ const playerConditions = {};
156
+ const summary = {};
157
+ players.forEach((player) => {
158
+ if (player?.notInSquad) return;
159
+ const key = getPlayerKey(player);
160
+ if (!key) return;
161
+ if (!playerConditions[key]) {
162
+ playerConditions[key] = {};
163
+ }
164
+ if (!player?.totalDamageDist) return;
165
+ player.totalDamageDist.forEach((distList) => {
166
+ if (!distList) return;
167
+ distList.forEach((entry) => {
168
+ if (!entry.id) return;
169
+ let skillName = `Skill ${entry.id}`;
170
+ let skillIcon;
171
+ if (skillMap) {
172
+ if (skillMap[`s${entry.id}`]) {
173
+ skillName = skillMap[`s${entry.id}`].name || skillName;
174
+ skillIcon = skillMap[`s${entry.id}`].icon || skillIcon;
175
+ } else if (skillMap[`${entry.id}`]) {
176
+ skillName = skillMap[`${entry.id}`].name || skillName;
177
+ skillIcon = skillMap[`${entry.id}`].icon || skillIcon;
178
+ }
179
+ }
180
+ const buffMeta = resolveBuffMetaById(buffMap, entry.id);
181
+ if (skillName.startsWith("Skill ") && buffMeta?.name) {
182
+ skillName = buffMeta.name;
183
+ skillIcon = buffMeta.icon || skillIcon;
184
+ }
185
+ const conditionName = resolveConditionNameFromEntry(skillName, entry.id, buffMap);
186
+ if (!conditionName) return;
187
+ const buffName = buffMeta?.name;
188
+ const conditionIcon = conditionIconMap.get(conditionName) || buffMeta?.icon;
189
+ const skillLabel = skillName.startsWith("Skill ") ? buffName || conditionName : skillName;
190
+ const skillLabelIcon = skillIcon || buffMeta?.icon;
191
+ const connectedHits = Number(entry.connectedHits ?? 0);
192
+ const rawHits = Number(entry.hits ?? 0);
193
+ const hits = connectedHits > 0 ? connectedHits : rawHits;
194
+ const damage = Number(entry.totalDamage ?? 0);
195
+ if (!Number.isFinite(hits) && !Number.isFinite(damage)) return;
196
+ const existing = summary[conditionName] || {
197
+ name: conditionName,
198
+ icon: conditionIcon,
199
+ applications: 0,
200
+ damage: 0
201
+ };
202
+ existing.applications += Number.isFinite(hits) ? hits : 0;
203
+ existing.damage += Number.isFinite(damage) ? damage : 0;
204
+ if (!existing.icon && conditionIcon) existing.icon = conditionIcon;
205
+ summary[conditionName] = existing;
206
+ const playerConditionTotals = playerConditions[key][conditionName] || {
207
+ icon: conditionIcon,
208
+ applications: 0,
209
+ damage: 0,
210
+ skills: {}
211
+ };
212
+ playerConditionTotals.applications += Number.isFinite(hits) ? hits : 0;
213
+ playerConditionTotals.damage += Number.isFinite(damage) ? damage : 0;
214
+ const skillEntry = playerConditionTotals.skills[skillLabel] || { name: skillLabel, hits: 0, damage: 0, icon: skillLabelIcon };
215
+ skillEntry.hits += Number.isFinite(hits) ? hits : 0;
216
+ skillEntry.damage += Number.isFinite(damage) ? damage : 0;
217
+ if (!skillEntry.icon && skillLabelIcon) skillEntry.icon = skillLabelIcon;
218
+ playerConditionTotals.skills[skillLabel] = skillEntry;
219
+ if (!playerConditionTotals.icon && conditionIcon) playerConditionTotals.icon = conditionIcon;
220
+ playerConditions[key][conditionName] = playerConditionTotals;
221
+ });
222
+ });
223
+ });
224
+ const nameToKey = /* @__PURE__ */ new Map();
225
+ players.forEach((player) => {
226
+ if (player?.notInSquad) return;
227
+ const key = getPlayerKey(player);
228
+ if (!key) return;
229
+ if (player?.name) {
230
+ nameToKey.set(player.name, key);
231
+ }
232
+ });
233
+ let buffStateApplicationsTotal = 0;
234
+ let buffStateSourcesSeen = 0;
235
+ let targetBuffEntriesSeen = 0;
236
+ targets.forEach((target) => {
237
+ if (!target?.buffs) return;
238
+ target.buffs.forEach((buff) => {
239
+ const buffId = Number(buff?.id);
240
+ if (!Number.isFinite(buffId)) return;
241
+ const buffMeta = resolveBuffMetaById(buffMap, buffId);
242
+ const normalizedName = getConditionName(buffMeta?.name);
243
+ if (!normalizedName) return;
244
+ if (buffMeta?.classification && buffMeta.classification !== "Condition") return;
245
+ const conditionName = normalizedName;
246
+ if (!conditionName) return;
247
+ const statesPerSource = buff.statesPerSource || {};
248
+ targetBuffEntriesSeen += 1;
249
+ Object.entries(statesPerSource).forEach(([sourceName, states]) => {
250
+ const key = nameToKey.get(sourceName);
251
+ if (!key) return;
252
+ buffStateSourcesSeen += 1;
253
+ const appliedCounts = countAppliedFromStates(states);
254
+ const activeCounts = countActiveStateEntries(states);
255
+ const uptimeMs = computeUptimeFromStates(states);
256
+ buffStateApplicationsTotal += appliedCounts;
257
+ const playerConditionTotals = playerConditions[key]?.[conditionName] || {
258
+ applications: 0,
259
+ damage: 0,
260
+ skills: {}
261
+ };
262
+ playerConditionTotals.applicationsFromBuffs = (playerConditionTotals.applicationsFromBuffs || 0) + appliedCounts;
263
+ playerConditionTotals.applicationsFromBuffsActive = (playerConditionTotals.applicationsFromBuffsActive || 0) + activeCounts;
264
+ playerConditionTotals.uptimeMs = (playerConditionTotals.uptimeMs || 0) + uptimeMs;
265
+ playerConditions[key] = playerConditions[key] || {};
266
+ playerConditions[key][conditionName] = playerConditionTotals;
267
+ const overallTotals = summary[conditionName] || {
268
+ name: conditionName,
269
+ applications: 0,
270
+ damage: 0
271
+ };
272
+ overallTotals.applicationsFromBuffs = (overallTotals.applicationsFromBuffs || 0) + appliedCounts;
273
+ overallTotals.applicationsFromBuffsActive = (overallTotals.applicationsFromBuffsActive || 0) + activeCounts;
274
+ overallTotals.uptimeMs = (overallTotals.uptimeMs || 0) + uptimeMs;
275
+ summary[conditionName] = overallTotals;
276
+ });
277
+ });
278
+ });
279
+ return {
280
+ playerConditions,
281
+ summary,
282
+ meta: {
283
+ buffStateApplicationsTotal,
284
+ targetBuffEntriesSeen,
285
+ buffStateSourcesSeen
286
+ }
287
+ };
288
+ };
289
+
290
+ export {
291
+ NON_DAMAGING_CONDITIONS,
292
+ getDefaultConditionIcon,
293
+ resolveBuffMetaById,
294
+ normalizeConditionLabel,
295
+ buildConditionIconMap,
296
+ resolveConditionNameFromEntry,
297
+ computeOutgoingConditions
298
+ };
@@ -0,0 +1,197 @@
1
+ // src/reportMetrics.ts
2
+ var ReportSchemaError = class extends Error {
3
+ };
4
+ var num = (value) => {
5
+ const n = Number(value ?? 0);
6
+ return Number.isFinite(n) ? n : 0;
7
+ };
8
+ var extractRunSummary = (report) => {
9
+ const payload = report;
10
+ const id = String(payload?.meta?.id ?? "").trim();
11
+ if (!id) throw new ReportSchemaError("report has no meta.id \u2014 not an AxiBridge report.json");
12
+ const stats = payload?.stats ?? {};
13
+ const warnings = [];
14
+ const byAccount = /* @__PURE__ */ new Map();
15
+ const ensure = (row) => {
16
+ const account = String(row?.account ?? "").trim();
17
+ if (!account || account === "Unknown") return null;
18
+ let entry = byAccount.get(account);
19
+ if (!entry) {
20
+ entry = {
21
+ account,
22
+ profession: String(row?.profession ?? "Unknown"),
23
+ professionList: Array.isArray(row?.professionList) ? row.professionList.map(String) : [],
24
+ combatTimeMs: 0,
25
+ squadTimeMs: 0,
26
+ classTimes: [],
27
+ damage: 0,
28
+ downContribution: 0,
29
+ kills: 0,
30
+ downsCaused: 0,
31
+ strips: 0,
32
+ cleanses: 0,
33
+ resurrects: 0,
34
+ healing: 0,
35
+ barrier: 0,
36
+ hasHealAddon: false,
37
+ damageTaken: 0,
38
+ downs: 0,
39
+ deaths: 0,
40
+ logsJoined: 0
41
+ };
42
+ byAccount.set(account, entry);
43
+ }
44
+ return entry;
45
+ };
46
+ const tables = ["offensePlayers", "supportPlayers", "healingPlayers", "defensePlayers", "generalPlayers", "attendanceData"];
47
+ if (!tables.some((key) => Array.isArray(stats?.[key]) && stats[key].length > 0)) {
48
+ warnings.push("no player tables in report");
49
+ }
50
+ for (const row of Array.isArray(stats?.offensePlayers) ? stats.offensePlayers : []) {
51
+ const p = ensure(row);
52
+ if (!p) continue;
53
+ p.damage += num(row?.offenseTotals?.damage);
54
+ p.downContribution += num(row?.offenseTotals?.downContribution);
55
+ p.kills += num(row?.offenseTotals?.killed);
56
+ p.downsCaused += num(row?.offenseTotals?.downed);
57
+ p.strips = Math.max(p.strips, num(row?.offenseTotals?.boonStrips));
58
+ }
59
+ for (const row of Array.isArray(stats?.supportPlayers) ? stats.supportPlayers : []) {
60
+ const p = ensure(row);
61
+ if (!p) continue;
62
+ p.cleanses += num(row?.supportTotals?.condiCleanse);
63
+ p.strips = Math.max(p.strips, num(row?.supportTotals?.boonStrips));
64
+ p.resurrects += num(row?.supportTotals?.resurrects);
65
+ p.logsJoined = Math.max(p.logsJoined, num(row?.logsJoined));
66
+ }
67
+ for (const row of Array.isArray(stats?.healingPlayers) ? stats.healingPlayers : []) {
68
+ const p = ensure(row);
69
+ if (!p) continue;
70
+ p.healing += num(row?.healingTotals?.squadHealing ?? row?.healingTotals?.healing);
71
+ p.barrier += num(row?.healingTotals?.squadBarrier ?? row?.healingTotals?.barrier);
72
+ if (row?.hasHealAddon === true) p.hasHealAddon = true;
73
+ }
74
+ for (const row of Array.isArray(stats?.defensePlayers) ? stats.defensePlayers : []) {
75
+ const p = ensure(row);
76
+ if (!p) continue;
77
+ p.damageTaken += num(row?.defenseTotals?.damageTaken);
78
+ p.downs += num(row?.defenseTotals?.downCount);
79
+ p.deaths += num(row?.defenseTotals?.deadCount);
80
+ }
81
+ for (const row of Array.isArray(stats?.generalPlayers) ? stats.generalPlayers : []) {
82
+ const p = ensure(row);
83
+ if (!p) continue;
84
+ p.combatTimeMs = Math.max(p.combatTimeMs, num(row?.squadActiveMs ?? row?.totalFightMs));
85
+ p.logsJoined = Math.max(p.logsJoined, num(row?.logsJoined));
86
+ }
87
+ for (const row of Array.isArray(stats?.attendanceData) ? stats.attendanceData : []) {
88
+ const p = ensure(row);
89
+ if (!p) continue;
90
+ p.combatTimeMs = Math.max(p.combatTimeMs, num(row?.combatTimeMs));
91
+ p.squadTimeMs = Math.max(p.squadTimeMs, num(row?.squadTimeMs));
92
+ if (Array.isArray(row?.classTimes)) {
93
+ p.classTimes = row.classTimes.map((c) => ({ profession: String(c?.profession ?? ""), timeMs: num(c?.timeMs) })).filter((c) => c.profession && c.timeMs > 0);
94
+ }
95
+ }
96
+ return {
97
+ id,
98
+ title: String(payload?.meta?.title ?? id),
99
+ dateStart: payload?.meta?.dateStart ?? null,
100
+ dateEnd: payload?.meta?.dateEnd ?? null,
101
+ fights: num(stats?.total),
102
+ wins: num(stats?.wins),
103
+ losses: num(stats?.losses),
104
+ avgSquadSize: typeof stats?.avgSquadSize === "number" ? stats.avgSquadSize : null,
105
+ avgEnemies: typeof stats?.avgEnemies === "number" ? stats.avgEnemies : null,
106
+ squadDeaths: num(stats?.totalSquadDeaths),
107
+ squadDowns: num(stats?.totalSquadDowns),
108
+ enemyDeaths: num(stats?.totalEnemyDeaths),
109
+ enemyDowns: num(stats?.totalEnemyDowns),
110
+ commanders: Array.isArray(payload?.meta?.commanders) ? payload.meta.commanders.map(String) : [],
111
+ players: Array.from(byAccount.values()),
112
+ warnings
113
+ };
114
+ };
115
+ var aggregatePlayers = (summaries, accounts) => {
116
+ const wanted = accounts && accounts.length > 0 ? new Set(accounts.map((a) => a.toLowerCase())) : null;
117
+ const map = /* @__PURE__ */ new Map();
118
+ for (const run of summaries) {
119
+ for (const p of run.players) {
120
+ if (wanted && !wanted.has(p.account.toLowerCase())) continue;
121
+ let agg = map.get(p.account);
122
+ if (!agg) {
123
+ agg = {
124
+ account: p.account,
125
+ runsJoined: 0,
126
+ combatTimeMs: 0,
127
+ squadTimeMs: 0,
128
+ professionTimeMs: {},
129
+ damage: 0,
130
+ dps: 0,
131
+ downContribution: 0,
132
+ kills: 0,
133
+ strips: 0,
134
+ cleanses: 0,
135
+ resurrects: 0,
136
+ healing: 0,
137
+ barrier: 0,
138
+ damageTaken: 0,
139
+ downs: 0,
140
+ deaths: 0,
141
+ lastSeen: null
142
+ };
143
+ map.set(p.account, agg);
144
+ }
145
+ agg.runsJoined += 1;
146
+ agg.combatTimeMs += p.combatTimeMs;
147
+ agg.squadTimeMs += p.squadTimeMs;
148
+ for (const c of p.classTimes) {
149
+ agg.professionTimeMs[c.profession] = (agg.professionTimeMs[c.profession] || 0) + c.timeMs;
150
+ }
151
+ agg.damage += p.damage;
152
+ agg.downContribution += p.downContribution;
153
+ agg.kills += p.kills;
154
+ agg.strips += p.strips;
155
+ agg.cleanses += p.cleanses;
156
+ agg.resurrects += p.resurrects;
157
+ agg.healing += p.healing;
158
+ agg.barrier += p.barrier;
159
+ agg.damageTaken += p.damageTaken;
160
+ agg.downs += p.downs;
161
+ agg.deaths += p.deaths;
162
+ const seen = run.dateEnd ?? run.dateStart;
163
+ if (seen && (!agg.lastSeen || seen > agg.lastSeen)) agg.lastSeen = seen;
164
+ }
165
+ }
166
+ const rows = Array.from(map.values());
167
+ for (const row of rows) {
168
+ row.dps = row.combatTimeMs > 0 ? row.damage / (row.combatTimeMs / 1e3) : 0;
169
+ }
170
+ return rows.sort((a, b) => b.damage - a.damage);
171
+ };
172
+ var RUN_SET_METRICS = [
173
+ "fights",
174
+ "wins",
175
+ "losses",
176
+ "squadDeaths",
177
+ "squadDowns",
178
+ "enemyDeaths",
179
+ "enemyDowns"
180
+ ];
181
+ var compareRunSets = (a, b) => {
182
+ const total = (runs, metric) => runs.reduce((sum, run) => sum + num(run[metric]), 0);
183
+ return {
184
+ metrics: RUN_SET_METRICS.map((metric) => {
185
+ const va = total(a, metric);
186
+ const vb = total(b, metric);
187
+ return { metric, a: va, b: vb, delta: vb - va, deltaPct: va !== 0 ? (vb - va) / va : null };
188
+ })
189
+ };
190
+ };
191
+
192
+ export {
193
+ ReportSchemaError,
194
+ extractRunSummary,
195
+ aggregatePlayers,
196
+ compareRunSets
197
+ };