fansunited-data-layer 0.13.0 → 0.14.2
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.d.ts.map +1 -1
- package/api/fansunited/http.js +70 -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/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.js +154 -0
- package/api/sportal365-sports/football/statistics/index.js.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-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-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/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 -3553
- 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/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/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-BvHU-bP0.js +0 -1795
- package/matches-Ci0Ci_Mw.cjs +0 -1
|
@@ -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;"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
function calculateStreak(matches, teamId, filter, venueFilter) {
|
|
2
|
+
const teamMatches = matches.filter((m) => {
|
|
3
|
+
const isHome = m.competitorOne.id === teamId;
|
|
4
|
+
const isAway = m.competitorTwo.id === teamId;
|
|
5
|
+
if (!isHome && !isAway) return false;
|
|
6
|
+
if (m.status?.type !== "FINISHED") return false;
|
|
7
|
+
if (venueFilter === "home" && !isHome) return false;
|
|
8
|
+
if (venueFilter === "away" && !isAway) return false;
|
|
9
|
+
return true;
|
|
10
|
+
}).sort((a, b) => new Date(b.timing.kickoffTime).getTime() - new Date(a.timing.kickoffTime).getTime());
|
|
11
|
+
if (teamMatches.length === 0) return null;
|
|
12
|
+
let streak = 0;
|
|
13
|
+
for (const match of teamMatches) {
|
|
14
|
+
const isHome = match.competitorOne.id === teamId;
|
|
15
|
+
if (filter(match, teamId, isHome)) {
|
|
16
|
+
streak++;
|
|
17
|
+
} else {
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return streak > 0 ? streak : null;
|
|
22
|
+
}
|
|
23
|
+
const streakFilters = {
|
|
24
|
+
win: (m, _teamId, isHome) => {
|
|
25
|
+
const teamScore = isHome ? parseInt(m.score?.competitorOne || "0") : parseInt(m.score?.competitorTwo || "0");
|
|
26
|
+
const oppScore = isHome ? parseInt(m.score?.competitorTwo || "0") : parseInt(m.score?.competitorOne || "0");
|
|
27
|
+
return teamScore > oppScore;
|
|
28
|
+
},
|
|
29
|
+
draw: (m) => {
|
|
30
|
+
return m.score?.competitorOne === m.score?.competitorTwo;
|
|
31
|
+
},
|
|
32
|
+
loss: (m, _teamId, isHome) => {
|
|
33
|
+
const teamScore = isHome ? parseInt(m.score?.competitorOne || "0") : parseInt(m.score?.competitorTwo || "0");
|
|
34
|
+
const oppScore = isHome ? parseInt(m.score?.competitorTwo || "0") : parseInt(m.score?.competitorOne || "0");
|
|
35
|
+
return teamScore < oppScore;
|
|
36
|
+
},
|
|
37
|
+
noWin: (m, teamId, isHome) => !streakFilters.win(m, teamId, isHome),
|
|
38
|
+
noDraw: (m) => !streakFilters.draw(m),
|
|
39
|
+
noDefeat: (m, teamId, isHome) => !streakFilters.loss(m, teamId, isHome),
|
|
40
|
+
scored: (m, _teamId, isHome) => {
|
|
41
|
+
const teamScore = isHome ? parseInt(m.score?.competitorOne || "0") : parseInt(m.score?.competitorTwo || "0");
|
|
42
|
+
return teamScore >= 1;
|
|
43
|
+
},
|
|
44
|
+
conceded: (m, _teamId, isHome) => {
|
|
45
|
+
const oppScore = isHome ? parseInt(m.score?.competitorTwo || "0") : parseInt(m.score?.competitorOne || "0");
|
|
46
|
+
return oppScore >= 1;
|
|
47
|
+
},
|
|
48
|
+
noGoalScored: (m, teamId, isHome) => !streakFilters.scored(m, teamId, isHome),
|
|
49
|
+
noGoalConceded: (m, teamId, isHome) => !streakFilters.conceded(m, teamId, isHome),
|
|
50
|
+
over25: (m) => {
|
|
51
|
+
const total = parseInt(m.score?.competitorOne || "0") + parseInt(m.score?.competitorTwo || "0");
|
|
52
|
+
return total > 2;
|
|
53
|
+
},
|
|
54
|
+
under25: (m) => {
|
|
55
|
+
const total = parseInt(m.score?.competitorOne || "0") + parseInt(m.score?.competitorTwo || "0");
|
|
56
|
+
return total < 3;
|
|
57
|
+
},
|
|
58
|
+
scoredTwice: (m, _teamId, isHome) => {
|
|
59
|
+
const teamScore = isHome ? parseInt(m.score?.competitorOne || "0") : parseInt(m.score?.competitorTwo || "0");
|
|
60
|
+
return teamScore >= 2;
|
|
61
|
+
},
|
|
62
|
+
concededTwice: (m, _teamId, isHome) => {
|
|
63
|
+
const oppScore = isHome ? parseInt(m.score?.competitorTwo || "0") : parseInt(m.score?.competitorOne || "0");
|
|
64
|
+
return oppScore >= 2;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
function getTeamStreaks(matches, teamId, venueFilter) {
|
|
68
|
+
return {
|
|
69
|
+
win: calculateStreak(matches, teamId, streakFilters.win, venueFilter),
|
|
70
|
+
draw: calculateStreak(matches, teamId, streakFilters.draw, venueFilter),
|
|
71
|
+
loss: calculateStreak(matches, teamId, streakFilters.loss, venueFilter),
|
|
72
|
+
noWin: calculateStreak(matches, teamId, streakFilters.noWin, venueFilter),
|
|
73
|
+
noDraw: calculateStreak(matches, teamId, streakFilters.noDraw, venueFilter),
|
|
74
|
+
noDefeat: calculateStreak(matches, teamId, streakFilters.noDefeat, venueFilter),
|
|
75
|
+
scored: calculateStreak(matches, teamId, streakFilters.scored, venueFilter),
|
|
76
|
+
conceded: calculateStreak(matches, teamId, streakFilters.conceded, venueFilter),
|
|
77
|
+
noGoalScored: calculateStreak(matches, teamId, streakFilters.noGoalScored, venueFilter),
|
|
78
|
+
noGoalConceded: calculateStreak(matches, teamId, streakFilters.noGoalConceded, venueFilter),
|
|
79
|
+
over25: calculateStreak(matches, teamId, streakFilters.over25, venueFilter),
|
|
80
|
+
under25: calculateStreak(matches, teamId, streakFilters.under25, venueFilter),
|
|
81
|
+
scoredTwice: calculateStreak(matches, teamId, streakFilters.scoredTwice, venueFilter),
|
|
82
|
+
concededTwice: calculateStreak(matches, teamId, streakFilters.concededTwice, venueFilter)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function getTeamStreaksComparison(matches, homeTeamId, awayTeamId) {
|
|
86
|
+
const homeTeamHomeStreaks = getTeamStreaks(matches, homeTeamId, "home");
|
|
87
|
+
const homeTeamTotalStreaks = getTeamStreaks(matches, homeTeamId);
|
|
88
|
+
const awayTeamAwayStreaks = getTeamStreaks(matches, awayTeamId, "away");
|
|
89
|
+
const awayTeamTotalStreaks = getTeamStreaks(matches, awayTeamId);
|
|
90
|
+
return [
|
|
91
|
+
{
|
|
92
|
+
label: "Consecutive wins",
|
|
93
|
+
homeTeam: { home: homeTeamHomeStreaks.win, total: homeTeamTotalStreaks.win },
|
|
94
|
+
awayTeam: { total: awayTeamTotalStreaks.win, home: awayTeamAwayStreaks.win },
|
|
95
|
+
colorType: "neutral"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
label: "Consecutive draws",
|
|
99
|
+
homeTeam: { home: homeTeamHomeStreaks.draw, total: homeTeamTotalStreaks.draw },
|
|
100
|
+
awayTeam: { total: awayTeamTotalStreaks.draw, home: awayTeamAwayStreaks.draw },
|
|
101
|
+
colorType: "neutral"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
label: "Consecutive defeats",
|
|
105
|
+
homeTeam: { home: homeTeamHomeStreaks.loss, total: homeTeamTotalStreaks.loss },
|
|
106
|
+
awayTeam: { total: awayTeamTotalStreaks.loss, home: awayTeamAwayStreaks.loss },
|
|
107
|
+
colorType: "neutral"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
label: "No win",
|
|
111
|
+
homeTeam: { home: homeTeamHomeStreaks.noWin, total: homeTeamTotalStreaks.noWin },
|
|
112
|
+
awayTeam: { total: awayTeamTotalStreaks.noWin, home: awayTeamAwayStreaks.noWin },
|
|
113
|
+
colorType: "negative"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
label: "No draw",
|
|
117
|
+
homeTeam: { home: homeTeamHomeStreaks.noDraw, total: homeTeamTotalStreaks.noDraw },
|
|
118
|
+
awayTeam: { total: awayTeamTotalStreaks.noDraw, home: awayTeamAwayStreaks.noDraw },
|
|
119
|
+
colorType: "neutral"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
label: "No defeat",
|
|
123
|
+
homeTeam: { home: homeTeamHomeStreaks.noDefeat, total: homeTeamTotalStreaks.noDefeat },
|
|
124
|
+
awayTeam: { total: awayTeamTotalStreaks.noDefeat, home: awayTeamAwayStreaks.noDefeat },
|
|
125
|
+
colorType: "positive"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
label: "Scored at least once",
|
|
129
|
+
homeTeam: { home: homeTeamHomeStreaks.scored, total: homeTeamTotalStreaks.scored },
|
|
130
|
+
awayTeam: { total: awayTeamTotalStreaks.scored, home: awayTeamAwayStreaks.scored },
|
|
131
|
+
colorType: "positive"
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
label: "Conceded at least once",
|
|
135
|
+
homeTeam: { home: homeTeamHomeStreaks.conceded, total: homeTeamTotalStreaks.conceded },
|
|
136
|
+
awayTeam: { total: awayTeamTotalStreaks.conceded, home: awayTeamAwayStreaks.conceded },
|
|
137
|
+
colorType: "negative"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
label: "No goal scored",
|
|
141
|
+
homeTeam: { home: homeTeamHomeStreaks.noGoalScored, total: homeTeamTotalStreaks.noGoalScored },
|
|
142
|
+
awayTeam: { total: awayTeamTotalStreaks.noGoalScored, home: awayTeamAwayStreaks.noGoalScored },
|
|
143
|
+
colorType: "neutral"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
label: "No goal conceded",
|
|
147
|
+
homeTeam: { home: homeTeamHomeStreaks.noGoalConceded, total: homeTeamTotalStreaks.noGoalConceded },
|
|
148
|
+
awayTeam: { total: awayTeamTotalStreaks.noGoalConceded, home: awayTeamAwayStreaks.noGoalConceded },
|
|
149
|
+
colorType: "neutral"
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
label: "GS + GC over 2.5",
|
|
153
|
+
homeTeam: { home: homeTeamHomeStreaks.over25, total: homeTeamTotalStreaks.over25 },
|
|
154
|
+
awayTeam: { total: awayTeamTotalStreaks.over25, home: awayTeamAwayStreaks.over25 },
|
|
155
|
+
colorType: "neutral"
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
label: "GS + GC under 2.5",
|
|
159
|
+
homeTeam: { home: homeTeamHomeStreaks.under25, total: homeTeamTotalStreaks.under25 },
|
|
160
|
+
awayTeam: { total: awayTeamTotalStreaks.under25, home: awayTeamAwayStreaks.under25 },
|
|
161
|
+
colorType: "neutral"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
label: "Scored at least twice",
|
|
165
|
+
homeTeam: { home: homeTeamHomeStreaks.scoredTwice, total: homeTeamTotalStreaks.scoredTwice },
|
|
166
|
+
awayTeam: { total: awayTeamTotalStreaks.scoredTwice, home: awayTeamAwayStreaks.scoredTwice },
|
|
167
|
+
colorType: "positive"
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
label: "Conceded at least twice",
|
|
171
|
+
homeTeam: { home: homeTeamHomeStreaks.concededTwice, total: homeTeamTotalStreaks.concededTwice },
|
|
172
|
+
awayTeam: { total: awayTeamTotalStreaks.concededTwice, home: awayTeamAwayStreaks.concededTwice },
|
|
173
|
+
colorType: "negative"
|
|
174
|
+
}
|
|
175
|
+
];
|
|
176
|
+
}
|
|
177
|
+
export {
|
|
178
|
+
calculateStreak,
|
|
179
|
+
getTeamStreaks,
|
|
180
|
+
getTeamStreaksComparison,
|
|
181
|
+
streakFilters
|
|
182
|
+
};
|
|
183
|
+
//# sourceMappingURL=streaks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streaks.js","sources":["../../../../src/lib/utilities/stats/team/streaks.ts"],"sourcesContent":["/**\n * Team streak calculations (consecutive results).\n */\n\nimport type { FUSportsMatch } from \"../../../types/canonical\";\nimport type { MatchFilter, VenueFilter } from \"../core/types\";\n\nexport interface StreakData {\n home: number | null;\n total: number | null;\n}\n\nexport interface StreakRow {\n label: string;\n homeTeam: StreakData;\n awayTeam: StreakData;\n colorType: \"positive\" | \"negative\" | \"neutral\";\n}\n\n/**\n * Calculate streak for a team based on a filter condition.\n * Returns how many consecutive recent matches satisfy the condition.\n */\nexport function calculateStreak(\n matches: FUSportsMatch[],\n teamId: string,\n filter: MatchFilter,\n venueFilter?: VenueFilter\n): number | null {\n // Get team's finished matches sorted by date (most recent first)\n const teamMatches = matches\n .filter((m) => {\n const isHome = m.competitorOne.id === teamId;\n const isAway = m.competitorTwo.id === teamId;\n if (!isHome && !isAway) return false;\n if (m.status?.type !== \"FINISHED\") return false;\n if (venueFilter === \"home\" && !isHome) return false;\n if (venueFilter === \"away\" && !isAway) return false;\n return true;\n })\n .sort((a, b) => new Date(b.timing.kickoffTime).getTime() - new Date(a.timing.kickoffTime).getTime());\n\n if (teamMatches.length === 0) return null;\n\n let streak = 0;\n for (const match of teamMatches) {\n const isHome = match.competitorOne.id === teamId;\n if (filter(match, teamId, isHome)) {\n streak++;\n } else {\n break;\n }\n }\n return streak > 0 ? streak : null;\n}\n\n/**\n * Predefined streak filter functions.\n */\nexport const streakFilters = {\n win: (m: FUSportsMatch, _teamId: string, isHome: boolean) => {\n const teamScore = isHome ? parseInt(m.score?.competitorOne || \"0\") : parseInt(m.score?.competitorTwo || \"0\");\n const oppScore = isHome ? parseInt(m.score?.competitorTwo || \"0\") : parseInt(m.score?.competitorOne || \"0\");\n return teamScore > oppScore;\n },\n draw: (m: FUSportsMatch) => {\n return m.score?.competitorOne === m.score?.competitorTwo;\n },\n loss: (m: FUSportsMatch, _teamId: string, isHome: boolean) => {\n const teamScore = isHome ? parseInt(m.score?.competitorOne || \"0\") : parseInt(m.score?.competitorTwo || \"0\");\n const oppScore = isHome ? parseInt(m.score?.competitorTwo || \"0\") : parseInt(m.score?.competitorOne || \"0\");\n return teamScore < oppScore;\n },\n noWin: (m: FUSportsMatch, teamId: string, isHome: boolean) => !streakFilters.win(m, teamId, isHome),\n noDraw: (m: FUSportsMatch) => !streakFilters.draw(m),\n noDefeat: (m: FUSportsMatch, teamId: string, isHome: boolean) => !streakFilters.loss(m, teamId, isHome),\n scored: (m: FUSportsMatch, _teamId: string, isHome: boolean) => {\n const teamScore = isHome ? parseInt(m.score?.competitorOne || \"0\") : parseInt(m.score?.competitorTwo || \"0\");\n return teamScore >= 1;\n },\n conceded: (m: FUSportsMatch, _teamId: string, isHome: boolean) => {\n const oppScore = isHome ? parseInt(m.score?.competitorTwo || \"0\") : parseInt(m.score?.competitorOne || \"0\");\n return oppScore >= 1;\n },\n noGoalScored: (m: FUSportsMatch, teamId: string, isHome: boolean) => !streakFilters.scored(m, teamId, isHome),\n noGoalConceded: (m: FUSportsMatch, teamId: string, isHome: boolean) => !streakFilters.conceded(m, teamId, isHome),\n over25: (m: FUSportsMatch) => {\n const total = parseInt(m.score?.competitorOne || \"0\") + parseInt(m.score?.competitorTwo || \"0\");\n return total > 2;\n },\n under25: (m: FUSportsMatch) => {\n const total = parseInt(m.score?.competitorOne || \"0\") + parseInt(m.score?.competitorTwo || \"0\");\n return total < 3;\n },\n scoredTwice: (m: FUSportsMatch, _teamId: string, isHome: boolean) => {\n const teamScore = isHome ? parseInt(m.score?.competitorOne || \"0\") : parseInt(m.score?.competitorTwo || \"0\");\n return teamScore >= 2;\n },\n concededTwice: (m: FUSportsMatch, _teamId: string, isHome: boolean) => {\n const oppScore = isHome ? parseInt(m.score?.competitorTwo || \"0\") : parseInt(m.score?.competitorOne || \"0\");\n return oppScore >= 2;\n },\n};\n\n/**\n * Get all streaks for a team.\n */\nexport function getTeamStreaks(\n matches: FUSportsMatch[],\n teamId: string,\n venueFilter?: VenueFilter\n): Record<string, number | null> {\n return {\n win: calculateStreak(matches, teamId, streakFilters.win, venueFilter),\n draw: calculateStreak(matches, teamId, streakFilters.draw, venueFilter),\n loss: calculateStreak(matches, teamId, streakFilters.loss, venueFilter),\n noWin: calculateStreak(matches, teamId, streakFilters.noWin, venueFilter),\n noDraw: calculateStreak(matches, teamId, streakFilters.noDraw, venueFilter),\n noDefeat: calculateStreak(matches, teamId, streakFilters.noDefeat, venueFilter),\n scored: calculateStreak(matches, teamId, streakFilters.scored, venueFilter),\n conceded: calculateStreak(matches, teamId, streakFilters.conceded, venueFilter),\n noGoalScored: calculateStreak(matches, teamId, streakFilters.noGoalScored, venueFilter),\n noGoalConceded: calculateStreak(matches, teamId, streakFilters.noGoalConceded, venueFilter),\n over25: calculateStreak(matches, teamId, streakFilters.over25, venueFilter),\n under25: calculateStreak(matches, teamId, streakFilters.under25, venueFilter),\n scoredTwice: calculateStreak(matches, teamId, streakFilters.scoredTwice, venueFilter),\n concededTwice: calculateStreak(matches, teamId, streakFilters.concededTwice, venueFilter),\n };\n}\n\n/**\n * Get team streaks comparison for two teams.\n */\nexport function getTeamStreaksComparison(\n matches: FUSportsMatch[],\n homeTeamId: string,\n awayTeamId: string\n): StreakRow[] {\n // Get streaks for home team (home venue and total)\n const homeTeamHomeStreaks = getTeamStreaks(matches, homeTeamId, \"home\");\n const homeTeamTotalStreaks = getTeamStreaks(matches, homeTeamId);\n\n // Get streaks for away team (away venue and total)\n const awayTeamAwayStreaks = getTeamStreaks(matches, awayTeamId, \"away\");\n const awayTeamTotalStreaks = getTeamStreaks(matches, awayTeamId);\n\n return [\n {\n label: \"Consecutive wins\",\n homeTeam: { home: homeTeamHomeStreaks.win, total: homeTeamTotalStreaks.win },\n awayTeam: { total: awayTeamTotalStreaks.win, home: awayTeamAwayStreaks.win },\n colorType: \"neutral\",\n },\n {\n label: \"Consecutive draws\",\n homeTeam: { home: homeTeamHomeStreaks.draw, total: homeTeamTotalStreaks.draw },\n awayTeam: { total: awayTeamTotalStreaks.draw, home: awayTeamAwayStreaks.draw },\n colorType: \"neutral\",\n },\n {\n label: \"Consecutive defeats\",\n homeTeam: { home: homeTeamHomeStreaks.loss, total: homeTeamTotalStreaks.loss },\n awayTeam: { total: awayTeamTotalStreaks.loss, home: awayTeamAwayStreaks.loss },\n colorType: \"neutral\",\n },\n {\n label: \"No win\",\n homeTeam: { home: homeTeamHomeStreaks.noWin, total: homeTeamTotalStreaks.noWin },\n awayTeam: { total: awayTeamTotalStreaks.noWin, home: awayTeamAwayStreaks.noWin },\n colorType: \"negative\",\n },\n {\n label: \"No draw\",\n homeTeam: { home: homeTeamHomeStreaks.noDraw, total: homeTeamTotalStreaks.noDraw },\n awayTeam: { total: awayTeamTotalStreaks.noDraw, home: awayTeamAwayStreaks.noDraw },\n colorType: \"neutral\",\n },\n {\n label: \"No defeat\",\n homeTeam: { home: homeTeamHomeStreaks.noDefeat, total: homeTeamTotalStreaks.noDefeat },\n awayTeam: { total: awayTeamTotalStreaks.noDefeat, home: awayTeamAwayStreaks.noDefeat },\n colorType: \"positive\",\n },\n {\n label: \"Scored at least once\",\n homeTeam: { home: homeTeamHomeStreaks.scored, total: homeTeamTotalStreaks.scored },\n awayTeam: { total: awayTeamTotalStreaks.scored, home: awayTeamAwayStreaks.scored },\n colorType: \"positive\",\n },\n {\n label: \"Conceded at least once\",\n homeTeam: { home: homeTeamHomeStreaks.conceded, total: homeTeamTotalStreaks.conceded },\n awayTeam: { total: awayTeamTotalStreaks.conceded, home: awayTeamAwayStreaks.conceded },\n colorType: \"negative\",\n },\n {\n label: \"No goal scored\",\n homeTeam: { home: homeTeamHomeStreaks.noGoalScored, total: homeTeamTotalStreaks.noGoalScored },\n awayTeam: { total: awayTeamTotalStreaks.noGoalScored, home: awayTeamAwayStreaks.noGoalScored },\n colorType: \"neutral\",\n },\n {\n label: \"No goal conceded\",\n homeTeam: { home: homeTeamHomeStreaks.noGoalConceded, total: homeTeamTotalStreaks.noGoalConceded },\n awayTeam: { total: awayTeamTotalStreaks.noGoalConceded, home: awayTeamAwayStreaks.noGoalConceded },\n colorType: \"neutral\",\n },\n {\n label: \"GS + GC over 2.5\",\n homeTeam: { home: homeTeamHomeStreaks.over25, total: homeTeamTotalStreaks.over25 },\n awayTeam: { total: awayTeamTotalStreaks.over25, home: awayTeamAwayStreaks.over25 },\n colorType: \"neutral\",\n },\n {\n label: \"GS + GC under 2.5\",\n homeTeam: { home: homeTeamHomeStreaks.under25, total: homeTeamTotalStreaks.under25 },\n awayTeam: { total: awayTeamTotalStreaks.under25, home: awayTeamAwayStreaks.under25 },\n colorType: \"neutral\",\n },\n {\n label: \"Scored at least twice\",\n homeTeam: { home: homeTeamHomeStreaks.scoredTwice, total: homeTeamTotalStreaks.scoredTwice },\n awayTeam: { total: awayTeamTotalStreaks.scoredTwice, home: awayTeamAwayStreaks.scoredTwice },\n colorType: \"positive\",\n },\n {\n label: \"Conceded at least twice\",\n homeTeam: { home: homeTeamHomeStreaks.concededTwice, total: homeTeamTotalStreaks.concededTwice },\n awayTeam: { total: awayTeamTotalStreaks.concededTwice, home: awayTeamAwayStreaks.concededTwice },\n colorType: \"negative\",\n },\n ];\n}\n"],"names":[],"mappings":"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"}
|