fansunited-data-layer 0.12.1 → 0.14.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/api/fansunited/constants.js +5 -0
- package/api/fansunited/constants.js.map +1 -0
- package/api/fansunited/football/competition/index.js +21 -0
- package/api/fansunited/football/competition/index.js.map +1 -0
- package/api/fansunited/football/competition/transformer.js +41 -0
- package/api/fansunited/football/competition/transformer.js.map +1 -0
- package/api/fansunited/football/competitions/index.js +56 -0
- package/api/fansunited/football/competitions/index.js.map +1 -0
- package/api/fansunited/football/competitions/transformer.js +68 -0
- package/api/fansunited/football/competitions/transformer.js.map +1 -0
- package/api/fansunited/football/http.js +7 -0
- package/api/fansunited/football/http.js.map +1 -0
- package/api/fansunited/football/matches/index.js +132 -0
- package/api/fansunited/football/matches/index.js.map +1 -0
- package/api/fansunited/football/matches/transformer.js +148 -0
- package/api/fansunited/football/matches/transformer.js.map +1 -0
- package/api/fansunited/football/players/index.js +60 -0
- package/api/fansunited/football/players/index.js.map +1 -0
- package/api/fansunited/football/players/transformer.js +85 -0
- package/api/fansunited/football/players/transformer.js.map +1 -0
- package/api/fansunited/football/search/index.js +64 -0
- package/api/fansunited/football/search/index.js.map +1 -0
- package/api/fansunited/football/search/transformer.js +114 -0
- package/api/fansunited/football/search/transformer.js.map +1 -0
- package/api/fansunited/football/teams/index.js +66 -0
- package/api/fansunited/football/teams/index.js.map +1 -0
- package/api/fansunited/football/teams/transformer.js +53 -0
- package/api/fansunited/football/teams/transformer.js.map +1 -0
- package/api/fansunited/http.js +74 -0
- package/api/fansunited/http.js.map +1 -0
- package/api/fansunited/search/constants.js +5 -0
- package/api/fansunited/search/constants.js.map +1 -0
- package/api/fansunited/search/http.js +7 -0
- package/api/fansunited/search/http.js.map +1 -0
- package/api/fansunited/search/index.js +308 -0
- package/api/fansunited/search/index.js.map +1 -0
- package/api/fansunited/search/transformer.js +374 -0
- package/api/fansunited/search/transformer.js.map +1 -0
- package/api/fansunited-sdk/loyalty/matches.js +21 -0
- package/api/fansunited-sdk/loyalty/matches.js.map +1 -0
- package/api/fansunited-sdk/loyalty/template.js +52 -0
- package/api/fansunited-sdk/loyalty/template.js.map +1 -0
- package/api/fansunited-sdk/odds/matches.js +109 -0
- package/api/fansunited-sdk/odds/matches.js.map +1 -0
- package/api/sportal365-sports/basketball/games/index.js +32 -0
- package/api/sportal365-sports/basketball/games/index.js.map +1 -0
- package/api/sportal365-sports/basketball/games/transformers/game.js +137 -0
- package/api/sportal365-sports/basketball/games/transformers/game.js.map +1 -0
- package/api/sportal365-sports/basketball/http.js +7 -0
- package/api/sportal365-sports/basketball/http.js.map +1 -0
- package/api/sportal365-sports/constants.js +15 -0
- package/api/sportal365-sports/constants.js.map +1 -0
- package/api/sportal365-sports/football/competitions/index.js +52 -0
- package/api/sportal365-sports/football/competitions/index.js.map +1 -0
- package/api/sportal365-sports/football/competitions/utils.js +92 -0
- package/api/sportal365-sports/football/competitions/utils.js.map +1 -0
- package/api/sportal365-sports/football/http.js +7 -0
- package/api/sportal365-sports/football/http.js.map +1 -0
- package/api/sportal365-sports/football/index.d.ts +2 -2
- package/api/sportal365-sports/football/index.d.ts.map +1 -1
- package/api/sportal365-sports/football/matches/index.js +206 -0
- package/api/sportal365-sports/football/matches/index.js.map +1 -0
- package/api/sportal365-sports/football/matches/transformers/commentary.js +27 -0
- package/api/sportal365-sports/football/matches/transformers/commentary.js.map +1 -0
- package/api/sportal365-sports/football/matches/transformers/lineup.js +74 -0
- package/api/sportal365-sports/football/matches/transformers/lineup.js.map +1 -0
- package/api/sportal365-sports/football/matches/transformers/match-event.js +60 -0
- package/api/sportal365-sports/football/matches/transformers/match-event.js.map +1 -0
- package/api/sportal365-sports/football/matches/transformers/match.js +195 -0
- package/api/sportal365-sports/football/matches/transformers/match.js.map +1 -0
- package/api/sportal365-sports/football/matches/transformers/odds.js +11 -0
- package/api/sportal365-sports/football/matches/transformers/odds.js.map +1 -0
- package/api/sportal365-sports/football/matches/transformers/statistics.js +41 -0
- package/api/sportal365-sports/football/matches/transformers/statistics.js.map +1 -0
- package/api/sportal365-sports/football/search/index.js +42 -0
- package/api/sportal365-sports/football/search/index.js.map +1 -0
- package/api/sportal365-sports/football/search/search.transformer.js +166 -0
- package/api/sportal365-sports/football/search/search.transformer.js.map +1 -0
- package/api/sportal365-sports/football/standings/index.js +28 -0
- package/api/sportal365-sports/football/standings/index.js.map +1 -0
- package/api/sportal365-sports/football/standings/standing.transformer.js +107 -0
- package/api/sportal365-sports/football/standings/standing.transformer.js.map +1 -0
- package/api/sportal365-sports/football/statistics/index.d.ts +46 -1
- package/api/sportal365-sports/football/statistics/index.d.ts.map +1 -1
- package/api/sportal365-sports/football/statistics/index.js +154 -0
- package/api/sportal365-sports/football/statistics/index.js.map +1 -0
- package/api/sportal365-sports/football/statistics/player-career.transformer.d.ts +11 -0
- package/api/sportal365-sports/football/statistics/player-career.transformer.d.ts.map +1 -0
- package/api/sportal365-sports/football/statistics/player-career.transformer.js +77 -0
- package/api/sportal365-sports/football/statistics/player-career.transformer.js.map +1 -0
- package/api/sportal365-sports/football/statistics/player-career.types.d.ts +115 -0
- package/api/sportal365-sports/football/statistics/player-career.types.d.ts.map +1 -0
- package/api/sportal365-sports/football/statistics/player-recent.transformer.d.ts +11 -0
- package/api/sportal365-sports/football/statistics/player-recent.transformer.d.ts.map +1 -0
- package/api/sportal365-sports/football/statistics/player-recent.transformer.js +54 -0
- package/api/sportal365-sports/football/statistics/player-recent.transformer.js.map +1 -0
- package/api/sportal365-sports/football/statistics/player-recent.types.d.ts +31 -0
- package/api/sportal365-sports/football/statistics/player-recent.types.d.ts.map +1 -0
- package/api/sportal365-sports/football/statistics/player-season.transformer.js +147 -0
- package/api/sportal365-sports/football/statistics/player-season.transformer.js.map +1 -0
- package/api/sportal365-sports/football/teams/index.js +73 -0
- package/api/sportal365-sports/football/teams/index.js.map +1 -0
- package/api/sportal365-sports/football/teams/utils.js +196 -0
- package/api/sportal365-sports/football/teams/utils.js.map +1 -0
- package/api/sportal365-sports/http.js +58 -0
- package/api/sportal365-sports/http.js.map +1 -0
- package/api/sportal365-sports/index.d.ts +2 -2
- package/api/sportal365-sports/index.d.ts.map +1 -1
- package/api/sportal365-sports/search/http.js +7 -0
- package/api/sportal365-sports/search/http.js.map +1 -0
- package/api/sportal365-sports/search/index.js +61 -0
- package/api/sportal365-sports/search/index.js.map +1 -0
- package/api/sportal365-sports/search/search.transformer.js +254 -0
- package/api/sportal365-sports/search/search.transformer.js.map +1 -0
- package/api/sportal365-sports/shared/odds.transformer.js +65 -0
- package/api/sportal365-sports/shared/odds.transformer.js.map +1 -0
- package/api/sportal365-sports/shared/providerRef.helper.js +31 -0
- package/api/sportal365-sports/shared/providerRef.helper.js.map +1 -0
- package/api/sportal365-sports/standings/http.js +7 -0
- package/api/sportal365-sports/standings/http.js.map +1 -0
- package/api/sportal365-sports/standings/index.js +29 -0
- package/api/sportal365-sports/standings/index.js.map +1 -0
- package/api/sportal365-sports/standings/standing.transformer.js +185 -0
- package/api/sportal365-sports/standings/standing.transformer.js.map +1 -0
- package/api/sportal365-sports/statistics/http.js +7 -0
- package/api/sportal365-sports/statistics/http.js.map +1 -0
- package/api/sportal365-sports/statistics/index.js +25 -0
- package/api/sportal365-sports/statistics/index.js.map +1 -0
- package/api/sportal365-sports/statistics/team-stats.transformer.js +16 -0
- package/api/sportal365-sports/statistics/team-stats.transformer.js.map +1 -0
- package/api/sportal365-sports/tennis/http.js +7 -0
- package/api/sportal365-sports/tennis/http.js.map +1 -0
- package/api/sportal365-sports/tennis/matches/index.js +34 -0
- package/api/sportal365-sports/tennis/matches/index.js.map +1 -0
- package/api/sportal365-sports/tennis/matches/transformers/match.js +193 -0
- package/api/sportal365-sports/tennis/matches/transformers/match.js.map +1 -0
- package/cache/cache-manager.js +136 -0
- package/cache/cache-manager.js.map +1 -0
- package/cache/memory-store.js +29 -0
- package/cache/memory-store.js.map +1 -0
- package/client.js +38 -12337
- package/client.js.map +1 -0
- package/config/index.js +20 -0
- package/config/index.js.map +1 -0
- package/fansunited-data-layer.js +146 -3551
- package/fansunited-data-layer.js.map +1 -0
- package/helpers/competition.helpers.js +11 -0
- package/helpers/competition.helpers.js.map +1 -0
- package/helpers/player.helpers.js +165 -0
- package/helpers/player.helpers.js.map +1 -0
- package/helpers/player.hooks.js +34 -0
- package/helpers/player.hooks.js.map +1 -0
- package/helpers/team.helpers.js +216 -0
- package/helpers/team.helpers.js.map +1 -0
- package/index.d.ts +3 -3
- package/index.d.ts.map +1 -1
- package/package.json +6 -8
- package/providers/competition/hooks/useCompetitionStats.js +10 -0
- package/providers/competition/hooks/useCompetitionStats.js.map +1 -0
- package/providers/competition/hooks/useGoalDistribution.js +23 -0
- package/providers/competition/hooks/useGoalDistribution.js.map +1 -0
- package/providers/competition/hooks/useMatchHelpers.js +85 -0
- package/providers/competition/hooks/useMatchHelpers.js.map +1 -0
- package/providers/competition/hooks/useStandingsCalculations.js +89 -0
- package/providers/competition/hooks/useStandingsCalculations.js.map +1 -0
- package/providers/competition/hooks/useStandingsHelpers.js +61 -0
- package/providers/competition/hooks/useStandingsHelpers.js.map +1 -0
- package/providers/competition/hooks/useTeamPosition.js +15 -0
- package/providers/competition/hooks/useTeamPosition.js.map +1 -0
- package/providers/competition/hooks/useTeamResultsTable.js +86 -0
- package/providers/competition/hooks/useTeamResultsTable.js.map +1 -0
- package/providers/competition/utils/competitionStats.js +109 -0
- package/providers/competition/utils/competitionStats.js.map +1 -0
- package/providers/competition/utils/goalDistribution.js +64 -0
- package/providers/competition/utils/goalDistribution.js.map +1 -0
- package/providers/competition/utils/standingsCalculations.js +152 -0
- package/providers/competition/utils/standingsCalculations.js.map +1 -0
- package/providers/competition.context.js +14 -0
- package/providers/competition.context.js.map +1 -0
- package/providers/competition.provider.js +187 -0
- package/providers/competition.provider.js.map +1 -0
- package/providers/fansunited-config.context.js +6 -0
- package/providers/fansunited-config.context.js.map +1 -0
- package/providers/fansunited-config.hooks.js +20 -0
- package/providers/fansunited-config.hooks.js.map +1 -0
- package/providers/fansunited-config.provider.js +9 -0
- package/providers/fansunited-config.provider.js.map +1 -0
- package/providers/fansunited-sdk.hook.js +50 -0
- package/providers/fansunited-sdk.hook.js.map +1 -0
- package/providers/leaderboard/hooks/useMatchHelpers.js +30 -0
- package/providers/leaderboard/hooks/useMatchHelpers.js.map +1 -0
- package/providers/leaderboard/hooks/useTemplateHelpers.js +39 -0
- package/providers/leaderboard/hooks/useTemplateHelpers.js.map +1 -0
- package/providers/leaderboard.context.js +14 -0
- package/providers/leaderboard.context.js.map +1 -0
- package/providers/leaderboard.provider.js +98 -0
- package/providers/leaderboard.provider.js.map +1 -0
- package/providers/match/hooks/useBatchMatchOdds.js +72 -0
- package/providers/match/hooks/useBatchMatchOdds.js.map +1 -0
- package/providers/match/hooks/useEventHelpers.js +46 -0
- package/providers/match/hooks/useEventHelpers.js.map +1 -0
- package/providers/match/hooks/useHeadToHeadHelpers.js +52 -0
- package/providers/match/hooks/useHeadToHeadHelpers.js.map +1 -0
- package/providers/match/hooks/useLineupHelpers.js +53 -0
- package/providers/match/hooks/useLineupHelpers.js.map +1 -0
- package/providers/match/hooks/useMatchStandingsCalculations.js +332 -0
- package/providers/match/hooks/useMatchStandingsCalculations.js.map +1 -0
- package/providers/match/hooks/useMatchStatus.js +37 -0
- package/providers/match/hooks/useMatchStatus.js.map +1 -0
- package/providers/match/hooks/useOddsHelpers.js +54 -0
- package/providers/match/hooks/useOddsHelpers.js.map +1 -0
- package/providers/match/hooks/useScoreHelpers.js +34 -0
- package/providers/match/hooks/useScoreHelpers.js.map +1 -0
- package/providers/match/hooks/useStandingsHelpers.js +53 -0
- package/providers/match/hooks/useStandingsHelpers.js.map +1 -0
- package/providers/match/hooks/useStatisticsHelpers.js +40 -0
- package/providers/match/hooks/useStatisticsHelpers.js.map +1 -0
- package/providers/match/hooks/useTeamHelpers.js +38 -0
- package/providers/match/hooks/useTeamHelpers.js.map +1 -0
- package/providers/match/hooks/useTeamStatsHelpers.js +141 -0
- package/providers/match/hooks/useTeamStatsHelpers.js.map +1 -0
- package/providers/match.context.js +14 -0
- package/providers/match.context.js.map +1 -0
- package/providers/match.provider.js +176 -0
- package/providers/match.provider.js.map +1 -0
- package/providers/team/hooks/useFormStats.js +116 -0
- package/providers/team/hooks/useFormStats.js.map +1 -0
- package/providers/team/hooks/useMatchHelpers.js +46 -0
- package/providers/team/hooks/useMatchHelpers.js.map +1 -0
- package/providers/team/hooks/useSquadHelpers.js +67 -0
- package/providers/team/hooks/useSquadHelpers.js.map +1 -0
- package/providers/team.context.js +14 -0
- package/providers/team.context.js.map +1 -0
- package/providers/team.provider.js +98 -0
- package/providers/team.provider.js.map +1 -0
- package/types/canonical/index.d.ts +1 -1
- package/types/canonical/index.d.ts.map +1 -1
- package/types/canonical/statistics.types.d.ts +102 -0
- package/types/canonical/statistics.types.d.ts.map +1 -1
- package/utilities/stats/core/helpers.js +105 -0
- package/utilities/stats/core/helpers.js.map +1 -0
- package/utilities/stats/core/types.js +5 -0
- package/utilities/stats/core/types.js.map +1 -0
- package/utilities/stats/match/headToHead.js +83 -0
- package/utilities/stats/match/headToHead.js.map +1 -0
- package/utilities/stats/match/homeVsAway.js +140 -0
- package/utilities/stats/match/homeVsAway.js.map +1 -0
- package/utilities/stats/match/overUnder.js +91 -0
- package/utilities/stats/match/overUnder.js.map +1 -0
- package/utilities/stats/match/result.js +21 -0
- package/utilities/stats/match/result.js.map +1 -0
- package/utilities/stats/team/commonOpponents.js +77 -0
- package/utilities/stats/team/commonOpponents.js.map +1 -0
- package/utilities/stats/team/goalStats.js +50 -0
- package/utilities/stats/team/goalStats.js.map +1 -0
- package/utilities/stats/team/streaks.js +183 -0
- package/utilities/stats/team/streaks.js.map +1 -0
- package/client.cjs +0 -8
- package/fansunited-data-layer.cjs +0 -1
- package/matches-CB-dSXEA.js +0 -1633
- package/matches-DvC2VSyO.cjs +0 -1
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { GOAL_EVENT_TYPES } from "../core/types.js";
|
|
2
|
+
import { calcAverage, calcPercentage, analyzeMatch } from "../core/helpers.js";
|
|
3
|
+
function getTeamMatches(matches, teamId, filterType) {
|
|
4
|
+
return matches.filter((match) => {
|
|
5
|
+
const isHome = match.competitorOne.id === teamId;
|
|
6
|
+
const isAway = match.competitorTwo.id === teamId;
|
|
7
|
+
if (!isHome && !isAway) return false;
|
|
8
|
+
return filterType === "home" ? isHome : isAway;
|
|
9
|
+
}).map((match) => {
|
|
10
|
+
const isHome = match.competitorOne.id === teamId;
|
|
11
|
+
return analyzeMatch(match, teamId, isHome);
|
|
12
|
+
}).filter((analysis) => analysis !== null);
|
|
13
|
+
}
|
|
14
|
+
function getFirstGoalStats(matches, teamId, filterType, statType) {
|
|
15
|
+
let count = 0;
|
|
16
|
+
for (const match of matches) {
|
|
17
|
+
const isHome = match.competitorOne.id === teamId;
|
|
18
|
+
const isAway = match.competitorTwo.id === teamId;
|
|
19
|
+
if (!isHome && !isAway) continue;
|
|
20
|
+
if (filterType === "home" && !isHome) continue;
|
|
21
|
+
if (filterType === "away" && !isAway) continue;
|
|
22
|
+
if (match.status?.type !== "FINISHED") continue;
|
|
23
|
+
const mainEvents = match.mainEvents;
|
|
24
|
+
if (!mainEvents || !Array.isArray(mainEvents)) continue;
|
|
25
|
+
const goalEvents = mainEvents.filter((e) => e.type && GOAL_EVENT_TYPES.includes(e.type)).sort((a, b) => {
|
|
26
|
+
const minuteA = a.minute ?? 0;
|
|
27
|
+
const minuteB = b.minute ?? 0;
|
|
28
|
+
if (minuteA !== minuteB) return minuteA - minuteB;
|
|
29
|
+
return (a.injuryMinute ?? 0) - (b.injuryMinute ?? 0);
|
|
30
|
+
});
|
|
31
|
+
if (goalEvents.length === 0) continue;
|
|
32
|
+
const firstGoal = goalEvents[0];
|
|
33
|
+
const teamCompetitorPosition = isHome ? "ONE" : "TWO";
|
|
34
|
+
const isOwnGoal = firstGoal.type === "OWN_GOAL";
|
|
35
|
+
let teamScoredFirst;
|
|
36
|
+
if (isOwnGoal) {
|
|
37
|
+
teamScoredFirst = firstGoal.competitorPosition !== teamCompetitorPosition;
|
|
38
|
+
} else {
|
|
39
|
+
teamScoredFirst = firstGoal.competitorPosition === teamCompetitorPosition;
|
|
40
|
+
}
|
|
41
|
+
if (statType === "scored" && teamScoredFirst) {
|
|
42
|
+
count++;
|
|
43
|
+
} else if (statType === "conceded" && !teamScoredFirst) {
|
|
44
|
+
count++;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return count;
|
|
48
|
+
}
|
|
49
|
+
function getMatchesWithEventsCount(matches, teamId, filterType) {
|
|
50
|
+
let count = 0;
|
|
51
|
+
for (const match of matches) {
|
|
52
|
+
const isHome = match.competitorOne.id === teamId;
|
|
53
|
+
const isAway = match.competitorTwo.id === teamId;
|
|
54
|
+
if (!isHome && !isAway) continue;
|
|
55
|
+
if (filterType === "home" && !isHome) continue;
|
|
56
|
+
if (filterType === "away" && !isAway) continue;
|
|
57
|
+
if (match.status?.type !== "FINISHED") continue;
|
|
58
|
+
const mainEvents = match.mainEvents;
|
|
59
|
+
if (mainEvents && Array.isArray(mainEvents)) {
|
|
60
|
+
count++;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return count;
|
|
64
|
+
}
|
|
65
|
+
function getTeamHomeAwayStats(matches, teamId, filterType) {
|
|
66
|
+
const teamMatches = getTeamMatches(matches, teamId, filterType);
|
|
67
|
+
const played = teamMatches.length;
|
|
68
|
+
if (played === 0) {
|
|
69
|
+
return {
|
|
70
|
+
played: 0,
|
|
71
|
+
goalsForPerMatch: 0,
|
|
72
|
+
cleanSheets: 0,
|
|
73
|
+
wonToNil: 0,
|
|
74
|
+
scoringRate: 0,
|
|
75
|
+
scoredInBothHalves: 0,
|
|
76
|
+
scoredFirst: 0,
|
|
77
|
+
leadingAtHalfTime: 0,
|
|
78
|
+
goalsAgainstPerMatch: 0,
|
|
79
|
+
failedToScore: 0,
|
|
80
|
+
lostToNil: 0,
|
|
81
|
+
concedingRate: 0,
|
|
82
|
+
concededInBothHalves: 0,
|
|
83
|
+
concededFirst: 0,
|
|
84
|
+
losingAtHalfTime: 0,
|
|
85
|
+
avgCornersFor: 0,
|
|
86
|
+
avgCornersAgainst: 0
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const totalGoalsFor = teamMatches.reduce((sum, m) => sum + m.goalsFor, 0);
|
|
90
|
+
const totalGoalsAgainst = teamMatches.reduce((sum, m) => sum + m.goalsAgainst, 0);
|
|
91
|
+
const cleanSheetCount = teamMatches.filter((m) => m.goalsAgainst === 0).length;
|
|
92
|
+
const wonToNilCount = teamMatches.filter((m) => m.isWin && m.goalsAgainst === 0).length;
|
|
93
|
+
const scoredCount = teamMatches.filter((m) => m.goalsFor > 0).length;
|
|
94
|
+
const failedToScoreCount = teamMatches.filter((m) => m.goalsFor === 0).length;
|
|
95
|
+
const lostToNilCount = teamMatches.filter((m) => m.isLoss && m.goalsFor === 0).length;
|
|
96
|
+
const concededCount = teamMatches.filter((m) => m.goalsAgainst > 0).length;
|
|
97
|
+
const matchesWithHT = teamMatches.filter((m) => m.halfTimeGoalsFor !== null);
|
|
98
|
+
const htCount = matchesWithHT.length;
|
|
99
|
+
const scoredBothHalvesCount = matchesWithHT.filter((m) => {
|
|
100
|
+
const htGoals = m.halfTimeGoalsFor;
|
|
101
|
+
const secondHalfGoals = m.goalsFor - htGoals;
|
|
102
|
+
return htGoals > 0 && secondHalfGoals > 0;
|
|
103
|
+
}).length;
|
|
104
|
+
const scoredFirstCount = getFirstGoalStats(matches, teamId, filterType, "scored");
|
|
105
|
+
const matchesWithEventsCount = getMatchesWithEventsCount(matches, teamId, filterType);
|
|
106
|
+
const leadingAtHTCount = matchesWithHT.filter((m) => m.halfTimeGoalsFor > m.halfTimeGoalsAgainst).length;
|
|
107
|
+
const concededBothHalvesCount = matchesWithHT.filter((m) => {
|
|
108
|
+
const htConceded = m.halfTimeGoalsAgainst;
|
|
109
|
+
const secondHalfConceded = m.goalsAgainst - htConceded;
|
|
110
|
+
return htConceded > 0 && secondHalfConceded > 0;
|
|
111
|
+
}).length;
|
|
112
|
+
const concededFirstCount = getFirstGoalStats(matches, teamId, filterType, "conceded");
|
|
113
|
+
const losingAtHTCount = matchesWithHT.filter((m) => m.halfTimeGoalsAgainst > m.halfTimeGoalsFor).length;
|
|
114
|
+
const matchesWithCorners = teamMatches.filter((m) => m.cornersFor !== null);
|
|
115
|
+
const totalCornersFor = matchesWithCorners.reduce((sum, m) => sum + (m.cornersFor || 0), 0);
|
|
116
|
+
const totalCornersAgainst = matchesWithCorners.reduce((sum, m) => sum + (m.cornersAgainst || 0), 0);
|
|
117
|
+
return {
|
|
118
|
+
played,
|
|
119
|
+
goalsForPerMatch: calcAverage(totalGoalsFor, played),
|
|
120
|
+
cleanSheets: calcPercentage(cleanSheetCount, played),
|
|
121
|
+
wonToNil: calcPercentage(wonToNilCount, played),
|
|
122
|
+
scoringRate: calcPercentage(scoredCount, played),
|
|
123
|
+
scoredInBothHalves: calcPercentage(scoredBothHalvesCount, htCount),
|
|
124
|
+
scoredFirst: calcPercentage(scoredFirstCount, matchesWithEventsCount),
|
|
125
|
+
leadingAtHalfTime: calcPercentage(leadingAtHTCount, htCount),
|
|
126
|
+
goalsAgainstPerMatch: calcAverage(totalGoalsAgainst, played),
|
|
127
|
+
failedToScore: calcPercentage(failedToScoreCount, played),
|
|
128
|
+
lostToNil: calcPercentage(lostToNilCount, played),
|
|
129
|
+
concedingRate: calcPercentage(concededCount, played),
|
|
130
|
+
concededInBothHalves: calcPercentage(concededBothHalvesCount, htCount),
|
|
131
|
+
concededFirst: calcPercentage(concededFirstCount, matchesWithEventsCount),
|
|
132
|
+
losingAtHalfTime: calcPercentage(losingAtHTCount, htCount),
|
|
133
|
+
avgCornersFor: calcAverage(totalCornersFor, matchesWithCorners.length),
|
|
134
|
+
avgCornersAgainst: calcAverage(totalCornersAgainst, matchesWithCorners.length)
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
export {
|
|
138
|
+
getTeamHomeAwayStats
|
|
139
|
+
};
|
|
140
|
+
//# sourceMappingURL=homeVsAway.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"homeVsAway.js","sources":["../../../../src/lib/utilities/stats/match/homeVsAway.ts"],"sourcesContent":["/**\n * Home vs Away statistics calculations.\n * Compares the home team's home performance against the away team's away performance.\n */\n\nimport type { FUSportsMatch } from \"../../../types/canonical\";\nimport type { MatchAnalysis, VenueFilter } from \"../core/types\";\nimport { GOAL_EVENT_TYPES } from \"../core/types\";\nimport { analyzeMatch, calcPercentage, calcAverage } from \"../core/helpers\";\n\nexport interface HomeVsAwayStats {\n // Match counts\n played: number;\n\n // Offensive stats\n goalsForPerMatch: number;\n cleanSheets: number; // percentage\n wonToNil: number; // percentage - won without conceding\n scoringRate: number; // percentage of matches where team scored\n scoredInBothHalves: number; // percentage\n scoredFirst: number; // percentage\n leadingAtHalfTime: number; // percentage\n\n // Defensive stats (negative indicators)\n goalsAgainstPerMatch: number;\n failedToScore: number; // percentage\n lostToNil: number; // percentage - lost without scoring\n concedingRate: number; // percentage of matches where team conceded\n concededInBothHalves: number; // percentage\n concededFirst: number; // percentage\n losingAtHalfTime: number; // percentage\n\n // Corners (if available)\n avgCornersFor: number;\n avgCornersAgainst: number;\n}\n\n/**\n * Get team's matches filtered by home/away.\n */\nfunction getTeamMatches(matches: FUSportsMatch[], teamId: string, filterType: VenueFilter): MatchAnalysis[] {\n return matches\n .filter((match) => {\n const isHome = match.competitorOne.id === teamId;\n const isAway = match.competitorTwo.id === teamId;\n if (!isHome && !isAway) return false;\n return filterType === \"home\" ? isHome : isAway;\n })\n .map((match) => {\n const isHome = match.competitorOne.id === teamId;\n return analyzeMatch(match, teamId, isHome);\n })\n .filter((analysis): analysis is MatchAnalysis => analysis !== null);\n}\n\n/**\n * Get count of matches where team scored first or conceded first\n * by analyzing mainEvents to find the first goal of each match.\n */\nfunction getFirstGoalStats(\n matches: FUSportsMatch[],\n teamId: string,\n filterType: VenueFilter,\n statType: \"scored\" | \"conceded\"\n): number {\n let count = 0;\n\n for (const match of matches) {\n // Check if team is in this match with the correct position\n const isHome = match.competitorOne.id === teamId;\n const isAway = match.competitorTwo.id === teamId;\n\n if (!isHome && !isAway) continue;\n if (filterType === \"home\" && !isHome) continue;\n if (filterType === \"away\" && !isAway) continue;\n\n // Only finished matches\n if (match.status?.type !== \"FINISHED\") continue;\n\n // Get mainEvents from the match (canonical format)\n const mainEvents = match.mainEvents;\n if (!mainEvents || !Array.isArray(mainEvents)) continue;\n\n // Find the first goal event (sorted by minute, then injuryMinute)\n const goalEvents = mainEvents\n .filter((e) => e.type && GOAL_EVENT_TYPES.includes(e.type as (typeof GOAL_EVENT_TYPES)[number]))\n .sort((a, b) => {\n const minuteA = a.minute ?? 0;\n const minuteB = b.minute ?? 0;\n if (minuteA !== minuteB) return minuteA - minuteB;\n return (a.injuryMinute ?? 0) - (b.injuryMinute ?? 0);\n });\n\n if (goalEvents.length === 0) continue; // No goals in this match\n\n const firstGoal = goalEvents[0];\n // competitorPosition: \"ONE\" = home team (competitorOne), \"TWO\" = away team\n const teamCompetitorPosition = isHome ? \"ONE\" : \"TWO\";\n\n // For OWN_GOAL, the competitorPosition is the team that scored the own goal\n // (i.e., conceded to themselves), so the opposite team benefits\n const isOwnGoal = firstGoal.type === \"OWN_GOAL\";\n\n let teamScoredFirst: boolean;\n if (isOwnGoal) {\n // Own goal: competitorPosition is who scored it against themselves\n // So if our team's position matches, WE conceded (opponent scored via our own goal)\n teamScoredFirst = firstGoal.competitorPosition !== teamCompetitorPosition;\n } else {\n // Regular goal or penalty: competitorPosition is who scored\n teamScoredFirst = firstGoal.competitorPosition === teamCompetitorPosition;\n }\n\n if (statType === \"scored\" && teamScoredFirst) {\n count++;\n } else if (statType === \"conceded\" && !teamScoredFirst) {\n count++;\n }\n }\n\n return count;\n}\n\n/**\n * Get count of matches with mainEvents data for accurate first goal stats.\n */\nfunction getMatchesWithEventsCount(matches: FUSportsMatch[], teamId: string, filterType: VenueFilter): number {\n let count = 0;\n\n for (const match of matches) {\n const isHome = match.competitorOne.id === teamId;\n const isAway = match.competitorTwo.id === teamId;\n\n if (!isHome && !isAway) continue;\n if (filterType === \"home\" && !isHome) continue;\n if (filterType === \"away\" && !isAway) continue;\n if (match.status?.type !== \"FINISHED\") continue;\n\n const mainEvents = match.mainEvents;\n if (mainEvents && Array.isArray(mainEvents)) {\n count++;\n }\n }\n\n return count;\n}\n\n/**\n * Get Home vs Away stats for a team.\n */\nexport function getTeamHomeAwayStats(\n matches: FUSportsMatch[],\n teamId: string,\n filterType: VenueFilter\n): HomeVsAwayStats {\n const teamMatches = getTeamMatches(matches, teamId, filterType);\n const played = teamMatches.length;\n\n if (played === 0) {\n return {\n played: 0,\n goalsForPerMatch: 0,\n cleanSheets: 0,\n wonToNil: 0,\n scoringRate: 0,\n scoredInBothHalves: 0,\n scoredFirst: 0,\n leadingAtHalfTime: 0,\n goalsAgainstPerMatch: 0,\n failedToScore: 0,\n lostToNil: 0,\n concedingRate: 0,\n concededInBothHalves: 0,\n concededFirst: 0,\n losingAtHalfTime: 0,\n avgCornersFor: 0,\n avgCornersAgainst: 0,\n };\n }\n\n // Calculate totals\n const totalGoalsFor = teamMatches.reduce((sum, m) => sum + m.goalsFor, 0);\n const totalGoalsAgainst = teamMatches.reduce((sum, m) => sum + m.goalsAgainst, 0);\n\n // Clean sheets (no goals conceded)\n const cleanSheetCount = teamMatches.filter((m) => m.goalsAgainst === 0).length;\n\n // Won to nil (won without conceding)\n const wonToNilCount = teamMatches.filter((m) => m.isWin && m.goalsAgainst === 0).length;\n\n // Scoring rate (scored at least 1)\n const scoredCount = teamMatches.filter((m) => m.goalsFor > 0).length;\n\n // Failed to score\n const failedToScoreCount = teamMatches.filter((m) => m.goalsFor === 0).length;\n\n // Lost to nil (lost without scoring)\n const lostToNilCount = teamMatches.filter((m) => m.isLoss && m.goalsFor === 0).length;\n\n // Conceding rate (conceded at least 1)\n const concededCount = teamMatches.filter((m) => m.goalsAgainst > 0).length;\n\n // Half-time based stats (only for matches with HT data)\n const matchesWithHT = teamMatches.filter((m) => m.halfTimeGoalsFor !== null);\n const htCount = matchesWithHT.length;\n\n // Scored in both halves\n const scoredBothHalvesCount = matchesWithHT.filter((m) => {\n const htGoals = m.halfTimeGoalsFor!;\n const secondHalfGoals = m.goalsFor - htGoals;\n return htGoals > 0 && secondHalfGoals > 0;\n }).length;\n\n // Scored first - team scored the opening goal of the match\n const scoredFirstCount = getFirstGoalStats(matches, teamId, filterType, \"scored\");\n const matchesWithEventsCount = getMatchesWithEventsCount(matches, teamId, filterType);\n\n // Leading at half-time\n const leadingAtHTCount = matchesWithHT.filter((m) => m.halfTimeGoalsFor! > m.halfTimeGoalsAgainst!).length;\n\n // Conceded in both halves\n const concededBothHalvesCount = matchesWithHT.filter((m) => {\n const htConceded = m.halfTimeGoalsAgainst!;\n const secondHalfConceded = m.goalsAgainst - htConceded;\n return htConceded > 0 && secondHalfConceded > 0;\n }).length;\n\n // Conceded first - opponent scored the opening goal of the match\n const concededFirstCount = getFirstGoalStats(matches, teamId, filterType, \"conceded\");\n\n // Losing at half-time\n const losingAtHTCount = matchesWithHT.filter((m) => m.halfTimeGoalsAgainst! > m.halfTimeGoalsFor!).length;\n\n // Corner stats\n const matchesWithCorners = teamMatches.filter((m) => m.cornersFor !== null);\n const totalCornersFor = matchesWithCorners.reduce((sum, m) => sum + (m.cornersFor || 0), 0);\n const totalCornersAgainst = matchesWithCorners.reduce((sum, m) => sum + (m.cornersAgainst || 0), 0);\n\n return {\n played,\n goalsForPerMatch: calcAverage(totalGoalsFor, played),\n cleanSheets: calcPercentage(cleanSheetCount, played),\n wonToNil: calcPercentage(wonToNilCount, played),\n scoringRate: calcPercentage(scoredCount, played),\n scoredInBothHalves: calcPercentage(scoredBothHalvesCount, htCount),\n scoredFirst: calcPercentage(scoredFirstCount, matchesWithEventsCount),\n leadingAtHalfTime: calcPercentage(leadingAtHTCount, htCount),\n goalsAgainstPerMatch: calcAverage(totalGoalsAgainst, played),\n failedToScore: calcPercentage(failedToScoreCount, played),\n lostToNil: calcPercentage(lostToNilCount, played),\n concedingRate: calcPercentage(concededCount, played),\n concededInBothHalves: calcPercentage(concededBothHalvesCount, htCount),\n concededFirst: calcPercentage(concededFirstCount, matchesWithEventsCount),\n losingAtHalfTime: calcPercentage(losingAtHTCount, htCount),\n avgCornersFor: calcAverage(totalCornersFor, matchesWithCorners.length),\n avgCornersAgainst: calcAverage(totalCornersAgainst, matchesWithCorners.length),\n };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
function getTeamMatchGoals(matches, teamId) {
|
|
2
|
+
return matches.filter((match) => {
|
|
3
|
+
if (match.status?.type !== "FINISHED") return false;
|
|
4
|
+
return match.competitorOne.id === teamId || match.competitorTwo.id === teamId;
|
|
5
|
+
}).map((match) => {
|
|
6
|
+
const isHome = match.competitorOne.id === teamId;
|
|
7
|
+
const score1 = parseInt(match.score?.competitorOne || "0");
|
|
8
|
+
const score2 = parseInt(match.score?.competitorTwo || "0");
|
|
9
|
+
const totalGoals = score1 + score2;
|
|
10
|
+
const periods = match.periods;
|
|
11
|
+
let halfTimeGoals = null;
|
|
12
|
+
if (periods && Array.isArray(periods)) {
|
|
13
|
+
const firstHalf = periods.find(
|
|
14
|
+
(p) => p.type === "FIRST_HALF" || p.type === "1ST_HALF"
|
|
15
|
+
);
|
|
16
|
+
if (firstHalf?.score) {
|
|
17
|
+
const ht1 = parseInt(firstHalf.score.competitorOne || "0");
|
|
18
|
+
const ht2 = parseInt(firstHalf.score.competitorTwo || "0");
|
|
19
|
+
halfTimeGoals = ht1 + ht2;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return { totalGoals, halfTimeGoals, isHome };
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function calculateOverPercentage(goals, threshold) {
|
|
26
|
+
if (goals.length === 0) return 0;
|
|
27
|
+
const overCount = goals.filter((g) => g > threshold).length;
|
|
28
|
+
return overCount / goals.length * 100;
|
|
29
|
+
}
|
|
30
|
+
function getTeamOverUnderStats(matches, teamId) {
|
|
31
|
+
const matchGoals = getTeamMatchGoals(matches, teamId);
|
|
32
|
+
const homeMatches = matchGoals.filter((m) => m.isHome);
|
|
33
|
+
const awayMatches = matchGoals.filter((m) => !m.isHome);
|
|
34
|
+
const allFTGoals = matchGoals.map((m) => m.totalGoals);
|
|
35
|
+
const homeFTGoals = homeMatches.map((m) => m.totalGoals);
|
|
36
|
+
const awayFTGoals = awayMatches.map((m) => m.totalGoals);
|
|
37
|
+
const allHTGoals = matchGoals.filter((m) => m.halfTimeGoals !== null).map((m) => m.halfTimeGoals);
|
|
38
|
+
const homeHTGoals = homeMatches.filter((m) => m.halfTimeGoals !== null).map((m) => m.halfTimeGoals);
|
|
39
|
+
const awayHTGoals = awayMatches.filter((m) => m.halfTimeGoals !== null).map((m) => m.halfTimeGoals);
|
|
40
|
+
return {
|
|
41
|
+
over05: {
|
|
42
|
+
home: calculateOverPercentage(homeFTGoals, 0.5),
|
|
43
|
+
total: calculateOverPercentage(allFTGoals, 0.5),
|
|
44
|
+
away: calculateOverPercentage(awayFTGoals, 0.5)
|
|
45
|
+
},
|
|
46
|
+
over15: {
|
|
47
|
+
home: calculateOverPercentage(homeFTGoals, 1.5),
|
|
48
|
+
total: calculateOverPercentage(allFTGoals, 1.5),
|
|
49
|
+
away: calculateOverPercentage(awayFTGoals, 1.5)
|
|
50
|
+
},
|
|
51
|
+
over25: {
|
|
52
|
+
home: calculateOverPercentage(homeFTGoals, 2.5),
|
|
53
|
+
total: calculateOverPercentage(allFTGoals, 2.5),
|
|
54
|
+
away: calculateOverPercentage(awayFTGoals, 2.5)
|
|
55
|
+
},
|
|
56
|
+
over35: {
|
|
57
|
+
home: calculateOverPercentage(homeFTGoals, 3.5),
|
|
58
|
+
total: calculateOverPercentage(allFTGoals, 3.5),
|
|
59
|
+
away: calculateOverPercentage(awayFTGoals, 3.5)
|
|
60
|
+
},
|
|
61
|
+
over45: {
|
|
62
|
+
home: calculateOverPercentage(homeFTGoals, 4.5),
|
|
63
|
+
total: calculateOverPercentage(allFTGoals, 4.5),
|
|
64
|
+
away: calculateOverPercentage(awayFTGoals, 4.5)
|
|
65
|
+
},
|
|
66
|
+
over55: {
|
|
67
|
+
home: calculateOverPercentage(homeFTGoals, 5.5),
|
|
68
|
+
total: calculateOverPercentage(allFTGoals, 5.5),
|
|
69
|
+
away: calculateOverPercentage(awayFTGoals, 5.5)
|
|
70
|
+
},
|
|
71
|
+
over05HT: {
|
|
72
|
+
home: calculateOverPercentage(homeHTGoals, 0.5),
|
|
73
|
+
total: calculateOverPercentage(allHTGoals, 0.5),
|
|
74
|
+
away: calculateOverPercentage(awayHTGoals, 0.5)
|
|
75
|
+
},
|
|
76
|
+
over15HT: {
|
|
77
|
+
home: calculateOverPercentage(homeHTGoals, 1.5),
|
|
78
|
+
total: calculateOverPercentage(allHTGoals, 1.5),
|
|
79
|
+
away: calculateOverPercentage(awayHTGoals, 1.5)
|
|
80
|
+
},
|
|
81
|
+
over25HT: {
|
|
82
|
+
home: calculateOverPercentage(homeHTGoals, 2.5),
|
|
83
|
+
total: calculateOverPercentage(allHTGoals, 2.5),
|
|
84
|
+
away: calculateOverPercentage(awayHTGoals, 2.5)
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export {
|
|
89
|
+
getTeamOverUnderStats
|
|
90
|
+
};
|
|
91
|
+
//# sourceMappingURL=overUnder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overUnder.js","sources":["../../../../src/lib/utilities/stats/match/overUnder.ts"],"sourcesContent":["/**\n * Over/Under goal threshold statistics calculations.\n */\n\nimport type { FUSportsMatch } from \"../../../types/canonical\";\n\n/**\n * Over/Under statistics for a team.\n */\nexport interface TeamOverUnderStats {\n // Full-time over thresholds (percentage of matches)\n over05: { home: number; total: number; away: number };\n over15: { home: number; total: number; away: number };\n over25: { home: number; total: number; away: number };\n over35: { home: number; total: number; away: number };\n over45: { home: number; total: number; away: number };\n over55: { home: number; total: number; away: number };\n // Half-time over thresholds (percentage of matches)\n over05HT: { home: number; total: number; away: number };\n over15HT: { home: number; total: number; away: number };\n over25HT: { home: number; total: number; away: number };\n}\n\ninterface MatchGoalData {\n totalGoals: number;\n halfTimeGoals: number | null;\n isHome: boolean;\n}\n\n/**\n * Get goal data for a team's matches.\n */\nfunction getTeamMatchGoals(matches: FUSportsMatch[], teamId: string): MatchGoalData[] {\n return matches\n .filter((match) => {\n // Only finished matches\n if (match.status?.type !== \"FINISHED\") return false;\n // Team must be in match\n return match.competitorOne.id === teamId || match.competitorTwo.id === teamId;\n })\n .map((match) => {\n const isHome = match.competitorOne.id === teamId;\n const score1 = parseInt(match.score?.competitorOne || \"0\");\n const score2 = parseInt(match.score?.competitorTwo || \"0\");\n const totalGoals = score1 + score2;\n\n // Try to get half-time score from periods if available\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const periods = (match as any).periods;\n let halfTimeGoals: number | null = null;\n if (periods && Array.isArray(periods)) {\n const firstHalf = periods.find(\n (p: { type?: string }) => p.type === \"FIRST_HALF\" || p.type === \"1ST_HALF\"\n );\n if (firstHalf?.score) {\n const ht1 = parseInt(firstHalf.score.competitorOne || \"0\");\n const ht2 = parseInt(firstHalf.score.competitorTwo || \"0\");\n halfTimeGoals = ht1 + ht2;\n }\n }\n\n return { totalGoals, halfTimeGoals, isHome };\n });\n}\n\n/**\n * Calculate percentage of matches over a threshold.\n */\nfunction calculateOverPercentage(goals: number[], threshold: number): number {\n if (goals.length === 0) return 0;\n const overCount = goals.filter((g) => g > threshold).length;\n return (overCount / goals.length) * 100;\n}\n\n/**\n * Calculate over/under statistics for a team from match data.\n */\nexport function getTeamOverUnderStats(matches: FUSportsMatch[], teamId: string): TeamOverUnderStats {\n const matchGoals = getTeamMatchGoals(matches, teamId);\n\n const homeMatches = matchGoals.filter((m) => m.isHome);\n const awayMatches = matchGoals.filter((m) => !m.isHome);\n\n const allFTGoals = matchGoals.map((m) => m.totalGoals);\n const homeFTGoals = homeMatches.map((m) => m.totalGoals);\n const awayFTGoals = awayMatches.map((m) => m.totalGoals);\n\n // Half-time goals (only for matches with HT data)\n const allHTGoals = matchGoals.filter((m) => m.halfTimeGoals !== null).map((m) => m.halfTimeGoals!);\n const homeHTGoals = homeMatches.filter((m) => m.halfTimeGoals !== null).map((m) => m.halfTimeGoals!);\n const awayHTGoals = awayMatches.filter((m) => m.halfTimeGoals !== null).map((m) => m.halfTimeGoals!);\n\n return {\n over05: {\n home: calculateOverPercentage(homeFTGoals, 0.5),\n total: calculateOverPercentage(allFTGoals, 0.5),\n away: calculateOverPercentage(awayFTGoals, 0.5),\n },\n over15: {\n home: calculateOverPercentage(homeFTGoals, 1.5),\n total: calculateOverPercentage(allFTGoals, 1.5),\n away: calculateOverPercentage(awayFTGoals, 1.5),\n },\n over25: {\n home: calculateOverPercentage(homeFTGoals, 2.5),\n total: calculateOverPercentage(allFTGoals, 2.5),\n away: calculateOverPercentage(awayFTGoals, 2.5),\n },\n over35: {\n home: calculateOverPercentage(homeFTGoals, 3.5),\n total: calculateOverPercentage(allFTGoals, 3.5),\n away: calculateOverPercentage(awayFTGoals, 3.5),\n },\n over45: {\n home: calculateOverPercentage(homeFTGoals, 4.5),\n total: calculateOverPercentage(allFTGoals, 4.5),\n away: calculateOverPercentage(awayFTGoals, 4.5),\n },\n over55: {\n home: calculateOverPercentage(homeFTGoals, 5.5),\n total: calculateOverPercentage(allFTGoals, 5.5),\n away: calculateOverPercentage(awayFTGoals, 5.5),\n },\n over05HT: {\n home: calculateOverPercentage(homeHTGoals, 0.5),\n total: calculateOverPercentage(allHTGoals, 0.5),\n away: calculateOverPercentage(awayHTGoals, 0.5),\n },\n over15HT: {\n home: calculateOverPercentage(homeHTGoals, 1.5),\n total: calculateOverPercentage(allHTGoals, 1.5),\n away: calculateOverPercentage(awayHTGoals, 1.5),\n },\n over25HT: {\n home: calculateOverPercentage(homeHTGoals, 2.5),\n total: calculateOverPercentage(allHTGoals, 2.5),\n away: calculateOverPercentage(awayHTGoals, 2.5),\n },\n };\n}\n\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
function getMatchResult(match, teamId) {
|
|
2
|
+
const isHome = match.competitorOne.id === teamId;
|
|
3
|
+
const isAway = match.competitorTwo.id === teamId;
|
|
4
|
+
if (!isHome && !isAway) return null;
|
|
5
|
+
if (!match.score) return null;
|
|
6
|
+
const teamScore = isHome ? parseInt(match.score.competitorOne || "0") : parseInt(match.score.competitorTwo || "0");
|
|
7
|
+
const opponentScore = isHome ? parseInt(match.score.competitorTwo || "0") : parseInt(match.score.competitorOne || "0");
|
|
8
|
+
let result;
|
|
9
|
+
if (teamScore > opponentScore) result = "W";
|
|
10
|
+
else if (teamScore < opponentScore) result = "L";
|
|
11
|
+
else result = "D";
|
|
12
|
+
const score = `${teamScore}:${opponentScore}`;
|
|
13
|
+
return {
|
|
14
|
+
result: { score, result, isHome },
|
|
15
|
+
isHome
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export {
|
|
19
|
+
getMatchResult
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=result.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"result.js","sources":["../../../../src/lib/utilities/stats/match/result.ts"],"sourcesContent":["/**\n * Match result calculation utilities.\n */\n\nimport type { FUSportsMatch } from \"../../../types/canonical\";\nimport type { MatchResult } from \"../core/types\";\n\n/**\n * Get match result from a team's perspective.\n */\nexport function getMatchResult(match: FUSportsMatch, teamId: string): { result: MatchResult; isHome: boolean } | null {\n const isHome = match.competitorOne.id === teamId;\n const isAway = match.competitorTwo.id === teamId;\n\n if (!isHome && !isAway) return null;\n if (!match.score) return null;\n\n const teamScore = isHome ? parseInt(match.score.competitorOne || \"0\") : parseInt(match.score.competitorTwo || \"0\");\n const opponentScore = isHome\n ? parseInt(match.score.competitorTwo || \"0\")\n : parseInt(match.score.competitorOne || \"0\");\n\n let result: \"W\" | \"D\" | \"L\";\n if (teamScore > opponentScore) result = \"W\";\n else if (teamScore < opponentScore) result = \"L\";\n else result = \"D\";\n\n // Score format: \"teamScore:opponentScore\" from team's perspective\n const score = `${teamScore}:${opponentScore}`;\n\n return {\n result: { score, result, isHome },\n isHome,\n };\n}\n\n"],"names":[],"mappings":"AAUO,SAAS,eAAe,OAAsB,QAAiE;AAClH,QAAM,SAAS,MAAM,cAAc,OAAO;AAC1C,QAAM,SAAS,MAAM,cAAc,OAAO;AAE1C,MAAI,CAAC,UAAU,CAAC,OAAQ,QAAO;AAC/B,MAAI,CAAC,MAAM,MAAO,QAAO;AAEzB,QAAM,YAAY,SAAS,SAAS,MAAM,MAAM,iBAAiB,GAAG,IAAI,SAAS,MAAM,MAAM,iBAAiB,GAAG;AACjH,QAAM,gBAAgB,SAChB,SAAS,MAAM,MAAM,iBAAiB,GAAG,IACzC,SAAS,MAAM,MAAM,iBAAiB,GAAG;AAE/C,MAAI;AACJ,MAAI,YAAY,cAAe,UAAS;AAAA,WAC/B,YAAY,cAAe,UAAS;AAAA,MACxC,UAAS;AAGd,QAAM,QAAQ,GAAG,SAAS,IAAI,aAAa;AAE3C,SAAO;AAAA,IACH,QAAQ,EAAE,OAAO,QAAQ,OAAA;AAAA,IACzB;AAAA,EAAA;AAER;"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { getMatchResult } from "../match/result.js";
|
|
2
|
+
function getTeamOpponentResults(matches, teamId) {
|
|
3
|
+
const resultsMap = /* @__PURE__ */ new Map();
|
|
4
|
+
matches.forEach((match) => {
|
|
5
|
+
if (match.status?.type !== "FINISHED") return;
|
|
6
|
+
const matchResult = getMatchResult(match, teamId);
|
|
7
|
+
if (!matchResult) return;
|
|
8
|
+
const opponentId = matchResult.isHome ? match.competitorTwo.id : match.competitorOne.id;
|
|
9
|
+
const opponent = matchResult.isHome ? match.competitorTwo : match.competitorOne;
|
|
10
|
+
const existing = resultsMap.get(opponentId) || {
|
|
11
|
+
opponentId,
|
|
12
|
+
opponentName: opponent.name,
|
|
13
|
+
opponentLogo: opponent.assets?.logo
|
|
14
|
+
};
|
|
15
|
+
if (matchResult.isHome) {
|
|
16
|
+
existing.homeResult = matchResult.result;
|
|
17
|
+
} else {
|
|
18
|
+
existing.awayResult = matchResult.result;
|
|
19
|
+
}
|
|
20
|
+
resultsMap.set(opponentId, existing);
|
|
21
|
+
});
|
|
22
|
+
return resultsMap;
|
|
23
|
+
}
|
|
24
|
+
function getTeamsCommonOpponents(matches, homeTeamId, awayTeamId, homeTeamName, awayTeamName, homeTeamLogo, awayTeamLogo) {
|
|
25
|
+
const homeTeamResults = getTeamOpponentResults(matches, homeTeamId);
|
|
26
|
+
const awayTeamResults = getTeamOpponentResults(matches, awayTeamId);
|
|
27
|
+
const allOpponentIds = /* @__PURE__ */ new Set([
|
|
28
|
+
...homeTeamResults.keys(),
|
|
29
|
+
...awayTeamResults.keys(),
|
|
30
|
+
homeTeamId,
|
|
31
|
+
// Include home team as row for away team results
|
|
32
|
+
awayTeamId
|
|
33
|
+
// Include away team as row for home team results
|
|
34
|
+
]);
|
|
35
|
+
const rows = [];
|
|
36
|
+
allOpponentIds.forEach((opponentId) => {
|
|
37
|
+
const homeTeamData = homeTeamResults.get(opponentId);
|
|
38
|
+
const awayTeamData = awayTeamResults.get(opponentId);
|
|
39
|
+
if (!homeTeamData && !awayTeamData && opponentId !== homeTeamId && opponentId !== awayTeamId) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
let opponentName;
|
|
43
|
+
let opponentLogo;
|
|
44
|
+
if (opponentId === homeTeamId) {
|
|
45
|
+
opponentName = homeTeamName;
|
|
46
|
+
opponentLogo = homeTeamLogo;
|
|
47
|
+
} else if (opponentId === awayTeamId) {
|
|
48
|
+
opponentName = awayTeamName;
|
|
49
|
+
opponentLogo = awayTeamLogo;
|
|
50
|
+
} else {
|
|
51
|
+
opponentName = homeTeamData?.opponentName || awayTeamData?.opponentName || "";
|
|
52
|
+
opponentLogo = homeTeamData?.opponentLogo || awayTeamData?.opponentLogo;
|
|
53
|
+
}
|
|
54
|
+
rows.push({
|
|
55
|
+
opponentId,
|
|
56
|
+
opponentName,
|
|
57
|
+
opponentLogo,
|
|
58
|
+
homeTeamHome: homeTeamData?.homeResult,
|
|
59
|
+
homeTeamAway: homeTeamData?.awayResult,
|
|
60
|
+
awayTeamHome: awayTeamData?.homeResult,
|
|
61
|
+
awayTeamAway: awayTeamData?.awayResult,
|
|
62
|
+
isHomeTeamRow: opponentId === homeTeamId,
|
|
63
|
+
isAwayTeamRow: opponentId === awayTeamId
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
return rows.sort((a, b) => {
|
|
67
|
+
if (a.isHomeTeamRow) return -1;
|
|
68
|
+
if (b.isHomeTeamRow) return 1;
|
|
69
|
+
if (a.isAwayTeamRow) return -1;
|
|
70
|
+
if (b.isAwayTeamRow) return 1;
|
|
71
|
+
return a.opponentName.localeCompare(b.opponentName);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
export {
|
|
75
|
+
getTeamsCommonOpponents
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=commonOpponents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commonOpponents.js","sources":["../../../../src/lib/utilities/stats/team/commonOpponents.ts"],"sourcesContent":["/**\n * Common opponents analysis - how two teams performed against shared opponents.\n */\n\nimport type { FUSportsMatch } from \"../../../types/canonical\";\nimport type { MatchResult } from \"../core/types\";\nimport { getMatchResult } from \"../match/result\";\n\nexport interface TeamOpponentResults {\n opponentId: string;\n opponentName: string;\n opponentLogo?: string;\n homeResult?: MatchResult;\n awayResult?: MatchResult;\n}\n\nexport interface TeamsResultsRow {\n opponentId: string;\n opponentName: string;\n opponentLogo?: string;\n // Results for home team (left side of table)\n homeTeamHome?: MatchResult;\n homeTeamAway?: MatchResult;\n // Results for away team (right side of table)\n awayTeamHome?: MatchResult;\n awayTeamAway?: MatchResult;\n // Is this row one of the two teams?\n isHomeTeamRow: boolean;\n isAwayTeamRow: boolean;\n}\n\n/**\n * Get opponent results for a specific team from finished matches.\n */\nfunction getTeamOpponentResults(matches: FUSportsMatch[], teamId: string): Map<string, TeamOpponentResults> {\n const resultsMap = new Map<string, TeamOpponentResults>();\n\n matches.forEach((match) => {\n // Only process finished matches\n if (match.status?.type !== \"FINISHED\") return;\n\n const matchResult = getMatchResult(match, teamId);\n if (!matchResult) return;\n\n const opponentId = matchResult.isHome ? match.competitorTwo.id : match.competitorOne.id;\n const opponent = matchResult.isHome ? match.competitorTwo : match.competitorOne;\n\n const existing = resultsMap.get(opponentId) || {\n opponentId,\n opponentName: opponent.name,\n opponentLogo: opponent.assets?.logo,\n };\n\n if (matchResult.isHome) {\n existing.homeResult = matchResult.result;\n } else {\n existing.awayResult = matchResult.result;\n }\n\n resultsMap.set(opponentId, existing);\n });\n\n return resultsMap;\n}\n\n/**\n * Get teams common opponents comparison.\n */\nexport function getTeamsCommonOpponents(\n matches: FUSportsMatch[],\n homeTeamId: string,\n awayTeamId: string,\n homeTeamName: string,\n awayTeamName: string,\n homeTeamLogo?: string,\n awayTeamLogo?: string\n): TeamsResultsRow[] {\n const homeTeamResults = getTeamOpponentResults(matches, homeTeamId);\n const awayTeamResults = getTeamOpponentResults(matches, awayTeamId);\n\n // Get all unique opponent IDs (including each team against the other)\n const allOpponentIds = new Set([\n ...homeTeamResults.keys(),\n ...awayTeamResults.keys(),\n homeTeamId, // Include home team as row for away team results\n awayTeamId, // Include away team as row for home team results\n ]);\n\n const rows: TeamsResultsRow[] = [];\n\n allOpponentIds.forEach((opponentId) => {\n const homeTeamData = homeTeamResults.get(opponentId);\n const awayTeamData = awayTeamResults.get(opponentId);\n\n // Skip if neither team has played this opponent\n if (!homeTeamData && !awayTeamData && opponentId !== homeTeamId && opponentId !== awayTeamId) {\n return;\n }\n\n let opponentName: string;\n let opponentLogo: string | undefined;\n\n if (opponentId === homeTeamId) {\n opponentName = homeTeamName;\n opponentLogo = homeTeamLogo;\n } else if (opponentId === awayTeamId) {\n opponentName = awayTeamName;\n opponentLogo = awayTeamLogo;\n } else {\n opponentName = homeTeamData?.opponentName || awayTeamData?.opponentName || \"\";\n opponentLogo = homeTeamData?.opponentLogo || awayTeamData?.opponentLogo;\n }\n\n rows.push({\n opponentId,\n opponentName,\n opponentLogo,\n homeTeamHome: homeTeamData?.homeResult,\n homeTeamAway: homeTeamData?.awayResult,\n awayTeamHome: awayTeamData?.homeResult,\n awayTeamAway: awayTeamData?.awayResult,\n isHomeTeamRow: opponentId === homeTeamId,\n isAwayTeamRow: opponentId === awayTeamId,\n });\n });\n\n // Sort: highlighted rows first, then alphabetically by opponent name\n return rows.sort((a, b) => {\n if (a.isHomeTeamRow) return -1;\n if (b.isHomeTeamRow) return 1;\n if (a.isAwayTeamRow) return -1;\n if (b.isAwayTeamRow) return 1;\n return a.opponentName.localeCompare(b.opponentName);\n });\n}\n"],"names":[],"mappings":";AAkCA,SAAS,uBAAuB,SAA0B,QAAkD;AACxG,QAAM,iCAAiB,IAAA;AAEvB,UAAQ,QAAQ,CAAC,UAAU;AAEvB,QAAI,MAAM,QAAQ,SAAS,WAAY;AAEvC,UAAM,cAAc,eAAe,OAAO,MAAM;AAChD,QAAI,CAAC,YAAa;AAElB,UAAM,aAAa,YAAY,SAAS,MAAM,cAAc,KAAK,MAAM,cAAc;AACrF,UAAM,WAAW,YAAY,SAAS,MAAM,gBAAgB,MAAM;AAElE,UAAM,WAAW,WAAW,IAAI,UAAU,KAAK;AAAA,MAC3C;AAAA,MACA,cAAc,SAAS;AAAA,MACvB,cAAc,SAAS,QAAQ;AAAA,IAAA;AAGnC,QAAI,YAAY,QAAQ;AACpB,eAAS,aAAa,YAAY;AAAA,IACtC,OAAO;AACH,eAAS,aAAa,YAAY;AAAA,IACtC;AAEA,eAAW,IAAI,YAAY,QAAQ;AAAA,EACvC,CAAC;AAED,SAAO;AACX;AAKO,SAAS,wBACZ,SACA,YACA,YACA,cACA,cACA,cACA,cACiB;AACjB,QAAM,kBAAkB,uBAAuB,SAAS,UAAU;AAClE,QAAM,kBAAkB,uBAAuB,SAAS,UAAU;AAGlE,QAAM,qCAAqB,IAAI;AAAA,IAC3B,GAAG,gBAAgB,KAAA;AAAA,IACnB,GAAG,gBAAgB,KAAA;AAAA,IACnB;AAAA;AAAA,IACA;AAAA;AAAA,EAAA,CACH;AAED,QAAM,OAA0B,CAAA;AAEhC,iBAAe,QAAQ,CAAC,eAAe;AACnC,UAAM,eAAe,gBAAgB,IAAI,UAAU;AACnD,UAAM,eAAe,gBAAgB,IAAI,UAAU;AAGnD,QAAI,CAAC,gBAAgB,CAAC,gBAAgB,eAAe,cAAc,eAAe,YAAY;AAC1F;AAAA,IACJ;AAEA,QAAI;AACJ,QAAI;AAEJ,QAAI,eAAe,YAAY;AAC3B,qBAAe;AACf,qBAAe;AAAA,IACnB,WAAW,eAAe,YAAY;AAClC,qBAAe;AACf,qBAAe;AAAA,IACnB,OAAO;AACH,qBAAe,cAAc,gBAAgB,cAAc,gBAAgB;AAC3E,qBAAe,cAAc,gBAAgB,cAAc;AAAA,IAC/D;AAEA,SAAK,KAAK;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA,cAAc,cAAc;AAAA,MAC5B,cAAc,cAAc;AAAA,MAC5B,cAAc,cAAc;AAAA,MAC5B,cAAc,cAAc;AAAA,MAC5B,eAAe,eAAe;AAAA,MAC9B,eAAe,eAAe;AAAA,IAAA,CACjC;AAAA,EACL,CAAC;AAGD,SAAO,KAAK,KAAK,CAAC,GAAG,MAAM;AACvB,QAAI,EAAE,cAAe,QAAO;AAC5B,QAAI,EAAE,cAAe,QAAO;AAC5B,QAAI,EAAE,cAAe,QAAO;AAC5B,QAAI,EAAE,cAAe,QAAO;AAC5B,WAAO,EAAE,aAAa,cAAc,EAAE,YAAY;AAAA,EACtD,CAAC;AACL;"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
function getTeamGoalStats(teamId, overallStandings, homeOffenceStandings, awayOffenceStandings, homeDefenceStandings, awayDefenceStandings) {
|
|
2
|
+
const overallEntry = overallStandings?.entries.find((e) => e.competitor.id === teamId);
|
|
3
|
+
const homeOffenceEntry = homeOffenceStandings?.entries.find((e) => e.competitor.id === teamId);
|
|
4
|
+
const awayOffenceEntry = awayOffenceStandings?.entries.find((e) => e.competitor.id === teamId);
|
|
5
|
+
const homeDefenceEntry = homeDefenceStandings?.entries.find((e) => e.competitor.id === teamId);
|
|
6
|
+
const awayDefenceEntry = awayDefenceStandings?.entries.find((e) => e.competitor.id === teamId);
|
|
7
|
+
if (!overallEntry) return null;
|
|
8
|
+
const homeGoals = homeOffenceEntry?.stats?.goalsFor ?? 0;
|
|
9
|
+
const awayGoals = awayOffenceEntry?.stats?.goalsFor ?? 0;
|
|
10
|
+
const totalGoals = overallEntry?.stats?.goalsFor ?? 0;
|
|
11
|
+
const homeGoalsConceded = homeDefenceEntry?.stats?.goalsAgainst ?? 0;
|
|
12
|
+
const awayGoalsConceded = awayDefenceEntry?.stats?.goalsAgainst ?? 0;
|
|
13
|
+
const totalGoalsConceded = overallEntry?.stats?.goalsAgainst ?? 0;
|
|
14
|
+
const homeGamesPlayed = homeOffenceEntry?.stats?.played ?? 0;
|
|
15
|
+
const awayGamesPlayed = awayOffenceEntry?.stats?.played ?? 0;
|
|
16
|
+
const totalGamesPlayed = overallEntry?.stats?.played ?? 0;
|
|
17
|
+
const gfPerMatch = totalGamesPlayed > 0 ? totalGoals / totalGamesPlayed : 0;
|
|
18
|
+
const gaPerMatch = totalGamesPlayed > 0 ? totalGoalsConceded / totalGamesPlayed : 0;
|
|
19
|
+
const gfGaPerMatch = gfPerMatch + gaPerMatch;
|
|
20
|
+
const homeGfPerMatch = homeGamesPlayed > 0 ? homeGoals / homeGamesPlayed : 0;
|
|
21
|
+
const awayGfPerMatch = awayGamesPlayed > 0 ? awayGoals / awayGamesPlayed : 0;
|
|
22
|
+
const homeGaPerMatch = homeGamesPlayed > 0 ? homeGoalsConceded / homeGamesPlayed : 0;
|
|
23
|
+
const awayGaPerMatch = awayGamesPlayed > 0 ? awayGoalsConceded / awayGamesPlayed : 0;
|
|
24
|
+
const homeGfGaPerMatch = homeGfPerMatch + homeGaPerMatch;
|
|
25
|
+
const awayGfGaPerMatch = awayGfPerMatch + awayGaPerMatch;
|
|
26
|
+
return {
|
|
27
|
+
homeGoals,
|
|
28
|
+
totalGoals,
|
|
29
|
+
awayGoals,
|
|
30
|
+
homeGamesPlayed,
|
|
31
|
+
awayGamesPlayed,
|
|
32
|
+
totalGamesPlayed,
|
|
33
|
+
homeGoalsConceded,
|
|
34
|
+
totalGoalsConceded,
|
|
35
|
+
awayGoalsConceded,
|
|
36
|
+
gfPerMatch,
|
|
37
|
+
gaPerMatch,
|
|
38
|
+
gfGaPerMatch,
|
|
39
|
+
homeGfPerMatch,
|
|
40
|
+
awayGfPerMatch,
|
|
41
|
+
homeGaPerMatch,
|
|
42
|
+
awayGaPerMatch,
|
|
43
|
+
homeGfGaPerMatch,
|
|
44
|
+
awayGfGaPerMatch
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export {
|
|
48
|
+
getTeamGoalStats
|
|
49
|
+
};
|
|
50
|
+
//# sourceMappingURL=goalStats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"goalStats.js","sources":["../../../../src/lib/utilities/stats/team/goalStats.ts"],"sourcesContent":["/**\n * Team goal statistics from standings data.\n */\n\nimport type { FUSportsStandings } from \"../../../types/canonical\";\n\nexport interface TeamGoalStats {\n // Goals scored\n homeGoals: number;\n totalGoals: number;\n awayGoals: number;\n homeGamesPlayed: number;\n awayGamesPlayed: number;\n totalGamesPlayed: number;\n\n // Goals conceded\n homeGoalsConceded: number;\n totalGoalsConceded: number;\n awayGoalsConceded: number;\n\n // Calculated averages\n gfPerMatch: number;\n gaPerMatch: number;\n gfGaPerMatch: number;\n\n // Home/Away specific averages\n homeGfPerMatch: number;\n awayGfPerMatch: number;\n homeGaPerMatch: number;\n awayGaPerMatch: number;\n\n // Home/Away specific combined\n homeGfGaPerMatch: number;\n awayGfGaPerMatch: number;\n}\n\n/**\n * Goal stat row definition for the comparison table.\n */\nexport interface GoalStatRow {\n id: string;\n label: string;\n homeTeamHomeValue: string | number;\n homeTeamTotalValue: string | number;\n awayTeamTotalValue: string | number;\n awayTeamAwayValue: string | number;\n highlightType?: \"positive\" | \"negative\" | \"neutral\";\n}\n\n/**\n * Get team goal stats from standings data.\n * Uses separate offence standings (for goals scored) and defence standings (for goals conceded).\n */\nexport function getTeamGoalStats(\n teamId: string,\n overallStandings: FUSportsStandings | undefined,\n homeOffenceStandings: FUSportsStandings | undefined,\n awayOffenceStandings: FUSportsStandings | undefined,\n homeDefenceStandings: FUSportsStandings | undefined,\n awayDefenceStandings: FUSportsStandings | undefined\n): TeamGoalStats | null {\n // Find team in each standings type\n const overallEntry = overallStandings?.entries.find((e) => e.competitor.id === teamId);\n const homeOffenceEntry = homeOffenceStandings?.entries.find((e) => e.competitor.id === teamId);\n const awayOffenceEntry = awayOffenceStandings?.entries.find((e) => e.competitor.id === teamId);\n const homeDefenceEntry = homeDefenceStandings?.entries.find((e) => e.competitor.id === teamId);\n const awayDefenceEntry = awayDefenceStandings?.entries.find((e) => e.competitor.id === teamId);\n\n if (!overallEntry) return null;\n\n // Goals scored from offence standings\n const homeGoals = homeOffenceEntry?.stats?.goalsFor ?? 0;\n const awayGoals = awayOffenceEntry?.stats?.goalsFor ?? 0;\n const totalGoals = overallEntry?.stats?.goalsFor ?? 0;\n\n // Goals conceded from defence standings\n const homeGoalsConceded = homeDefenceEntry?.stats?.goalsAgainst ?? 0;\n const awayGoalsConceded = awayDefenceEntry?.stats?.goalsAgainst ?? 0;\n const totalGoalsConceded = overallEntry?.stats?.goalsAgainst ?? 0;\n\n // Games played from offence standings (same as defence)\n const homeGamesPlayed = homeOffenceEntry?.stats?.played ?? 0;\n const awayGamesPlayed = awayOffenceEntry?.stats?.played ?? 0;\n const totalGamesPlayed = overallEntry?.stats?.played ?? 0;\n\n // Calculate averages\n const gfPerMatch = totalGamesPlayed > 0 ? totalGoals / totalGamesPlayed : 0;\n const gaPerMatch = totalGamesPlayed > 0 ? totalGoalsConceded / totalGamesPlayed : 0;\n const gfGaPerMatch = gfPerMatch + gaPerMatch;\n\n const homeGfPerMatch = homeGamesPlayed > 0 ? homeGoals / homeGamesPlayed : 0;\n const awayGfPerMatch = awayGamesPlayed > 0 ? awayGoals / awayGamesPlayed : 0;\n const homeGaPerMatch = homeGamesPlayed > 0 ? homeGoalsConceded / homeGamesPlayed : 0;\n const awayGaPerMatch = awayGamesPlayed > 0 ? awayGoalsConceded / awayGamesPlayed : 0;\n const homeGfGaPerMatch = homeGfPerMatch + homeGaPerMatch;\n const awayGfGaPerMatch = awayGfPerMatch + awayGaPerMatch;\n\n return {\n homeGoals,\n totalGoals,\n awayGoals,\n homeGamesPlayed,\n awayGamesPlayed,\n totalGamesPlayed,\n homeGoalsConceded,\n totalGoalsConceded,\n awayGoalsConceded,\n gfPerMatch,\n gaPerMatch,\n gfGaPerMatch,\n homeGfPerMatch,\n awayGfPerMatch,\n homeGaPerMatch,\n awayGaPerMatch,\n homeGfGaPerMatch,\n awayGfGaPerMatch,\n };\n}\n"],"names":[],"mappings":"AAqDO,SAAS,iBACZ,QACA,kBACA,sBACA,sBACA,sBACA,sBACoB;AAEpB,QAAM,eAAe,kBAAkB,QAAQ,KAAK,CAAC,MAAM,EAAE,WAAW,OAAO,MAAM;AACrF,QAAM,mBAAmB,sBAAsB,QAAQ,KAAK,CAAC,MAAM,EAAE,WAAW,OAAO,MAAM;AAC7F,QAAM,mBAAmB,sBAAsB,QAAQ,KAAK,CAAC,MAAM,EAAE,WAAW,OAAO,MAAM;AAC7F,QAAM,mBAAmB,sBAAsB,QAAQ,KAAK,CAAC,MAAM,EAAE,WAAW,OAAO,MAAM;AAC7F,QAAM,mBAAmB,sBAAsB,QAAQ,KAAK,CAAC,MAAM,EAAE,WAAW,OAAO,MAAM;AAE7F,MAAI,CAAC,aAAc,QAAO;AAG1B,QAAM,YAAY,kBAAkB,OAAO,YAAY;AACvD,QAAM,YAAY,kBAAkB,OAAO,YAAY;AACvD,QAAM,aAAa,cAAc,OAAO,YAAY;AAGpD,QAAM,oBAAoB,kBAAkB,OAAO,gBAAgB;AACnE,QAAM,oBAAoB,kBAAkB,OAAO,gBAAgB;AACnE,QAAM,qBAAqB,cAAc,OAAO,gBAAgB;AAGhE,QAAM,kBAAkB,kBAAkB,OAAO,UAAU;AAC3D,QAAM,kBAAkB,kBAAkB,OAAO,UAAU;AAC3D,QAAM,mBAAmB,cAAc,OAAO,UAAU;AAGxD,QAAM,aAAa,mBAAmB,IAAI,aAAa,mBAAmB;AAC1E,QAAM,aAAa,mBAAmB,IAAI,qBAAqB,mBAAmB;AAClF,QAAM,eAAe,aAAa;AAElC,QAAM,iBAAiB,kBAAkB,IAAI,YAAY,kBAAkB;AAC3E,QAAM,iBAAiB,kBAAkB,IAAI,YAAY,kBAAkB;AAC3E,QAAM,iBAAiB,kBAAkB,IAAI,oBAAoB,kBAAkB;AACnF,QAAM,iBAAiB,kBAAkB,IAAI,oBAAoB,kBAAkB;AACnF,QAAM,mBAAmB,iBAAiB;AAC1C,QAAM,mBAAmB,iBAAiB;AAE1C,SAAO;AAAA,IACH;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAER;"}
|