og-statistics 2.0.4 → 3.0.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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +251 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "og-statistics",
3
- "version": "2.0.4",
3
+ "version": "3.0.0",
4
4
  "description": "",
5
5
  "main": "./src/index.js",
6
6
  "scripts": {
package/src/index.js CHANGED
@@ -188,4 +188,254 @@ function filterSubs(incidents, isHome) {
188
188
  }
189
189
 
190
190
 
191
- module.exports = { filterStats, filterIncidents };
191
+ function goalSummary(incidentsData, windowMinutes = 15) {
192
+ try {
193
+ const { local = [], away = [] } = incidentsData;
194
+
195
+ // Normaliza goles de cada lado
196
+ function extractGoals(events, teamLabel) {
197
+ return events
198
+ .filter(e => e.incidentType === 'goal')
199
+ .map(e => ({
200
+ team: teamLabel, // 'home' | 'away'
201
+ time: e.time, // minuto
202
+ playerId: e.player?.id ?? null, // id jugador
203
+ playerName: e.player?.name ?? null // nombre jugador
204
+ }));
205
+ }
206
+
207
+ const homeGoals = extractGoals(local, 'home');
208
+ const awayGoals = extractGoals(away, 'away');
209
+ const goals = [...homeGoals, ...awayGoals].sort((a, b) => a.time - b.time);
210
+
211
+ const noGoals = goals.length === 0;
212
+
213
+ // Caso sin goles
214
+ if (noGoals) {
215
+ return {
216
+ noGoals: true,
217
+ goalsCount: { home: 0, away: 0, total: 0 },
218
+
219
+ firstScorerTeam: null,
220
+ firstScorerPlayer: null,
221
+ lastScorerTeam: null,
222
+ lastScorerPlayer: null,
223
+
224
+ earliestGoalByTeam: { home: null, away: null },
225
+ scoredFirst15: { home: false, away: false },
226
+
227
+ twoGoalsInWindow: { home: false, away: false },
228
+
229
+ goalsBySegment: {},
230
+
231
+ playersGoals: { home: [], away: [] },
232
+ multiScorersList: { home: [], away: [] },
233
+ multiScorers: { home: false, away: false, any: false },
234
+ hatTricks: { home: [], away: [] },
235
+
236
+ comeback: false,
237
+ leadChanges: 0
238
+ };
239
+ }
240
+
241
+ // 1. Conteo de goles
242
+ const goalsCount = {
243
+ home: homeGoals.length,
244
+ away: awayGoals.length,
245
+ total: goals.length
246
+ };
247
+
248
+ // 2. Primero y último goleador
249
+ const firstGoal = goals[0];
250
+ const lastGoal = goals[goals.length - 1];
251
+
252
+ const firstScorerTeam = firstGoal.team;
253
+ const lastScorerTeam = lastGoal.team;
254
+
255
+ const firstScorerPlayer = {
256
+ team: firstGoal.team,
257
+ playerId: firstGoal.playerId,
258
+ playerName: firstGoal.playerName,
259
+ minute: firstGoal.time
260
+ };
261
+
262
+ const lastScorerPlayer = {
263
+ team: lastGoal.team,
264
+ playerId: lastGoal.playerId,
265
+ playerName: lastGoal.playerName,
266
+ minute: lastGoal.time
267
+ };
268
+
269
+ // 3. Primer gol por equipo y si marcaron en los primeros 15
270
+ function earliestGoalMinute(goalArray) {
271
+ if (!goalArray.length) return null;
272
+ return goalArray.reduce((min, g) => Math.min(min, g.time), goalArray[0].time);
273
+ }
274
+
275
+ const earliestGoalByTeam = {
276
+ home: earliestGoalMinute(homeGoals),
277
+ away: earliestGoalMinute(awayGoals)
278
+ };
279
+
280
+ const scoredFirst15 = {
281
+ home: earliestGoalByTeam.home !== null && earliestGoalByTeam.home <= 15,
282
+ away: earliestGoalByTeam.away !== null && earliestGoalByTeam.away <= 15
283
+ };
284
+
285
+ // --- 4. 2+ goles de un equipo en un lapso de windowMinutes ---
286
+ function hasTwoGoalsInWindow(goalArray) {
287
+ if (goalArray.length < 2) return false;
288
+ const times = goalArray.map(g => g.time).sort((a, b) => a - b);
289
+
290
+ for (let i = 0; i < times.length; i++) {
291
+ for (let j = i + 1; j < times.length; j++) {
292
+ if (times[j] - times[i] <= windowMinutes) {
293
+ return true;
294
+ }
295
+ }
296
+ }
297
+ return false;
298
+ }
299
+
300
+ const twoGoalsInWindow = {
301
+ home: hasTwoGoalsInWindow(homeGoals),
302
+ away: hasTwoGoalsInWindow(awayGoals)
303
+ };
304
+
305
+ // --- 5. Goles por segmentos de tiempo ---
306
+ const segments = [
307
+ { label: '0-15', start: 0, end: 15 },
308
+ { label: '16-30', start: 16, end: 30 },
309
+ { label: '31-45', start: 31, end: 45 },
310
+ { label: '46-60', start: 46, end: 60 },
311
+ { label: '61-75', start: 61, end: 75 },
312
+ { label: '76-90', start: 76, end: 90 },
313
+ { label: '91+', start: 91, end: Infinity }
314
+ ];
315
+
316
+ const goalsBySegment = {};
317
+ for (const seg of segments) {
318
+ goalsBySegment[seg.label] = { home: 0, away: 0 };
319
+ }
320
+
321
+ for (const g of goals) {
322
+ const seg = segments.find(s => g.time >= s.start && g.time <= s.end);
323
+ if (seg) {
324
+ goalsBySegment[seg.label][g.team] += 1;
325
+ }
326
+ }
327
+
328
+ // --- 6. Goles por jugador (id + nombre) ---
329
+ function buildPlayersGoals(goalArray) {
330
+ const map = new Map(); // key: playerId, value: { playerId, playerName, count }
331
+
332
+ for (const g of goalArray) {
333
+ if (!g.playerId) continue;
334
+ const existing = map.get(g.playerId) || {
335
+ playerId: g.playerId,
336
+ playerName: g.playerName,
337
+ count: 0
338
+ };
339
+ existing.count += 1;
340
+ map.set(g.playerId, existing);
341
+ }
342
+
343
+ return Array.from(map.values());
344
+ }
345
+
346
+ const playersGoalsHome = buildPlayersGoals(homeGoals);
347
+ const playersGoalsAway = buildPlayersGoals(awayGoals);
348
+
349
+ const playersGoals = {
350
+ home: playersGoalsHome,
351
+ away: playersGoalsAway
352
+ };
353
+
354
+ // --- 7. Multiscorers (2+ goles) ---
355
+ const multiScorersList = {
356
+ home: playersGoalsHome.filter(p => p.count >= 2),
357
+ away: playersGoalsAway.filter(p => p.count >= 2)
358
+ };
359
+
360
+ const multiScorers = {
361
+ home: multiScorersList.home.length > 0,
362
+ away: multiScorersList.away.length > 0,
363
+ any:
364
+ multiScorersList.home.length > 0 ||
365
+ multiScorersList.away.length > 0
366
+ };
367
+
368
+ // --- 8. Hat-tricks (3+ goles) ---
369
+ const hatTricks = {
370
+ home: playersGoalsHome.filter(p => p.count >= 3),
371
+ away: playersGoalsAway.filter(p => p.count >= 3)
372
+ };
373
+
374
+ // --- 9. Timeline, remontada y cambios de líder ---
375
+ let homeScore = 0;
376
+ let awayScore = 0;
377
+ let everHomeBehind = false;
378
+ let everAwayBehind = false;
379
+ let leadChanges = 0;
380
+ let previousLeader = 'draw'; // 'home' | 'away' | 'draw'
381
+
382
+ for (const g of goals) {
383
+ if (g.team === 'home') homeScore += 1;
384
+ else awayScore += 1;
385
+
386
+ if (homeScore < awayScore) everHomeBehind = true;
387
+ if (awayScore < homeScore) everAwayBehind = true;
388
+
389
+ let currentLeader = 'draw';
390
+ if (homeScore > awayScore) currentLeader = 'home';
391
+ else if (awayScore > homeScore) currentLeader = 'away';
392
+
393
+ if (
394
+ currentLeader !== previousLeader &&
395
+ previousLeader !== 'draw' &&
396
+ currentLeader !== 'draw'
397
+ ) {
398
+ leadChanges += 1;
399
+ }
400
+
401
+ previousLeader = currentLeader;
402
+ }
403
+
404
+ const finalHome = homeScore;
405
+ const finalAway = awayScore;
406
+
407
+ let comeback = false;
408
+ if (finalHome > finalAway && everHomeBehind) comeback = true;
409
+ if (finalAway > finalHome && everAwayBehind) comeback = true;
410
+
411
+ // --- Resultado final ---
412
+ return {
413
+ noGoals,
414
+ goalsCount,
415
+
416
+ firstScorerTeam,
417
+ firstScorerPlayer,
418
+ lastScorerTeam,
419
+ lastScorerPlayer,
420
+
421
+ earliestGoalByTeam,
422
+ scoredFirst15,
423
+
424
+ twoGoalsInWindow,
425
+ goalsBySegment,
426
+
427
+ playersGoals,
428
+ multiScorersList,
429
+ multiScorers,
430
+ hatTricks,
431
+
432
+ comeback,
433
+ leadChanges
434
+ };
435
+ } catch (error) {
436
+ return { ok: false, error: { code: error.message, message: error.stack } }
437
+ }
438
+ }
439
+
440
+
441
+ module.exports = { filterStats, filterIncidents, goalSummary };