og-statistics 2.0.3 → 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 +255 -1
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -105,21 +105,25 @@ card
|
|
|
105
105
|
function filterIncidents(incidents) {
|
|
106
106
|
try {
|
|
107
107
|
|
|
108
|
+
const home = incidents.incidents.filter(s => s.isHome === true);
|
|
108
109
|
const home_cards = filterCards(incidents.incidents, true);
|
|
109
110
|
const home_goals = filterGoals(incidents.incidents, true);
|
|
110
111
|
const home_subs = filterSubs(incidents.incidents, true);
|
|
111
112
|
|
|
113
|
+
const away = incidents.incidents.filter(s => s.isHome === false);
|
|
112
114
|
const away_cards = filterCards(incidents.incidents, false);
|
|
113
115
|
const away_goals = filterGoals(incidents.incidents, false);
|
|
114
116
|
const away_subs = filterSubs(incidents.incidents, false);
|
|
115
117
|
|
|
116
118
|
return {
|
|
117
119
|
home: {
|
|
120
|
+
all: home,
|
|
118
121
|
cards: home_cards,
|
|
119
122
|
goals: home_goals,
|
|
120
123
|
subs: home_subs
|
|
121
124
|
},
|
|
122
125
|
away: {
|
|
126
|
+
all: away,
|
|
123
127
|
cards: away_cards,
|
|
124
128
|
goals: away_goals,
|
|
125
129
|
subs: away_subs
|
|
@@ -184,4 +188,254 @@ function filterSubs(incidents, isHome) {
|
|
|
184
188
|
}
|
|
185
189
|
|
|
186
190
|
|
|
187
|
-
|
|
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 };
|