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