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.
- package/package.json +1 -1
- package/src/index.js +251 -1
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -188,4 +188,254 @@ function filterSubs(incidents, isHome) {
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
|
|
191
|
-
|
|
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 };
|