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,67 @@
|
|
|
1
|
+
import { useMemo, useCallback } from "react";
|
|
2
|
+
function useSquadHelpers(squad) {
|
|
3
|
+
const players = useMemo(() => squad?.players ?? [], [squad]);
|
|
4
|
+
const getPlayer = useCallback((id) => players.find((p) => p.id === id), [players]);
|
|
5
|
+
const getPlayerByShirtNumber = useCallback(
|
|
6
|
+
(shirtNumber) => players.find((p) => p.shirtNumber === shirtNumber),
|
|
7
|
+
[players]
|
|
8
|
+
);
|
|
9
|
+
const getPlayersByPosition = useCallback(
|
|
10
|
+
(position) => players.filter((p) => p.position === position),
|
|
11
|
+
[players]
|
|
12
|
+
);
|
|
13
|
+
const getActiveSquad = useCallback(() => players.filter((p) => p.status === "ACTIVE"), [players]);
|
|
14
|
+
const getInactiveSquad = useCallback(() => players.filter((p) => p.status === "INACTIVE"), [players]);
|
|
15
|
+
const getSquadByContractType = useCallback(
|
|
16
|
+
(contractType) => players.filter((p) => p.contractType === contractType),
|
|
17
|
+
[players]
|
|
18
|
+
);
|
|
19
|
+
const getGoalkeepers = useCallback(
|
|
20
|
+
() => players.filter((p) => p.position?.toLowerCase().includes("keeper")),
|
|
21
|
+
[players]
|
|
22
|
+
);
|
|
23
|
+
const getDefenders = useCallback(
|
|
24
|
+
() => players.filter((p) => p.position?.toLowerCase().includes("defender")),
|
|
25
|
+
[players]
|
|
26
|
+
);
|
|
27
|
+
const getMidfielders = useCallback(
|
|
28
|
+
() => players.filter((p) => p.position?.toLowerCase().includes("midfielder")),
|
|
29
|
+
[players]
|
|
30
|
+
);
|
|
31
|
+
const getForwards = useCallback(
|
|
32
|
+
() => players.filter(
|
|
33
|
+
(p) => p.position?.toLowerCase().includes("forward") || p.position?.toLowerCase().includes("attacker")
|
|
34
|
+
),
|
|
35
|
+
[players]
|
|
36
|
+
);
|
|
37
|
+
return useMemo(
|
|
38
|
+
() => ({
|
|
39
|
+
getPlayer,
|
|
40
|
+
getPlayerByShirtNumber,
|
|
41
|
+
getPlayersByPosition,
|
|
42
|
+
getActiveSquad,
|
|
43
|
+
getInactiveSquad,
|
|
44
|
+
getSquadByContractType,
|
|
45
|
+
getGoalkeepers,
|
|
46
|
+
getDefenders,
|
|
47
|
+
getMidfielders,
|
|
48
|
+
getForwards
|
|
49
|
+
}),
|
|
50
|
+
[
|
|
51
|
+
getPlayer,
|
|
52
|
+
getPlayerByShirtNumber,
|
|
53
|
+
getPlayersByPosition,
|
|
54
|
+
getActiveSquad,
|
|
55
|
+
getInactiveSquad,
|
|
56
|
+
getSquadByContractType,
|
|
57
|
+
getGoalkeepers,
|
|
58
|
+
getDefenders,
|
|
59
|
+
getMidfielders,
|
|
60
|
+
getForwards
|
|
61
|
+
]
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
export {
|
|
65
|
+
useSquadHelpers
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=useSquadHelpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSquadHelpers.js","sources":["../../../../src/lib/providers/team/hooks/useSquadHelpers.ts"],"sourcesContent":["import { useCallback, useMemo } from \"react\";\nimport type { FUSportsSquad } from \"../../../types/canonical\";\n\n/**\n * Hook for squad-related helper functions\n */\nexport function useSquadHelpers(squad: FUSportsSquad | null) {\n const players = useMemo(() => squad?.players ?? [], [squad]);\n\n const getPlayer = useCallback((id: string) => players.find((p) => p.id === id), [players]);\n\n const getPlayerByShirtNumber = useCallback(\n (shirtNumber: number) => players.find((p) => p.shirtNumber === shirtNumber),\n [players]\n );\n\n const getPlayersByPosition = useCallback(\n (position: string) => players.filter((p) => p.position === position),\n [players]\n );\n\n const getActiveSquad = useCallback(() => players.filter((p) => p.status === \"ACTIVE\"), [players]);\n\n const getInactiveSquad = useCallback(() => players.filter((p) => p.status === \"INACTIVE\"), [players]);\n\n const getSquadByContractType = useCallback(\n (contractType: \"PERMANENT\" | \"LOAN\" | \"UNKNOWN\") => players.filter((p) => p.contractType === contractType),\n [players]\n );\n\n const getGoalkeepers = useCallback(\n () => players.filter((p) => p.position?.toLowerCase().includes(\"keeper\")),\n [players]\n );\n\n const getDefenders = useCallback(\n () => players.filter((p) => p.position?.toLowerCase().includes(\"defender\")),\n [players]\n );\n\n const getMidfielders = useCallback(\n () => players.filter((p) => p.position?.toLowerCase().includes(\"midfielder\")),\n [players]\n );\n\n const getForwards = useCallback(\n () =>\n players.filter(\n (p) => p.position?.toLowerCase().includes(\"forward\") || p.position?.toLowerCase().includes(\"attacker\")\n ),\n [players]\n );\n\n return useMemo(\n () => ({\n getPlayer,\n getPlayerByShirtNumber,\n getPlayersByPosition,\n getActiveSquad,\n getInactiveSquad,\n getSquadByContractType,\n getGoalkeepers,\n getDefenders,\n getMidfielders,\n getForwards,\n }),\n [\n getPlayer,\n getPlayerByShirtNumber,\n getPlayersByPosition,\n getActiveSquad,\n getInactiveSquad,\n getSquadByContractType,\n getGoalkeepers,\n getDefenders,\n getMidfielders,\n getForwards,\n ]\n );\n}\n"],"names":[],"mappings":";AAMO,SAAS,gBAAgB,OAA6B;AACzD,QAAM,UAAU,QAAQ,MAAM,OAAO,WAAW,CAAA,GAAI,CAAC,KAAK,CAAC;AAE3D,QAAM,YAAY,YAAY,CAAC,OAAe,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC;AAEzF,QAAM,yBAAyB;AAAA,IAC3B,CAAC,gBAAwB,QAAQ,KAAK,CAAC,MAAM,EAAE,gBAAgB,WAAW;AAAA,IAC1E,CAAC,OAAO;AAAA,EAAA;AAGZ,QAAM,uBAAuB;AAAA,IACzB,CAAC,aAAqB,QAAQ,OAAO,CAAC,MAAM,EAAE,aAAa,QAAQ;AAAA,IACnE,CAAC,OAAO;AAAA,EAAA;AAGZ,QAAM,iBAAiB,YAAY,MAAM,QAAQ,OAAO,CAAC,MAAM,EAAE,WAAW,QAAQ,GAAG,CAAC,OAAO,CAAC;AAEhG,QAAM,mBAAmB,YAAY,MAAM,QAAQ,OAAO,CAAC,MAAM,EAAE,WAAW,UAAU,GAAG,CAAC,OAAO,CAAC;AAEpG,QAAM,yBAAyB;AAAA,IAC3B,CAAC,iBAAmD,QAAQ,OAAO,CAAC,MAAM,EAAE,iBAAiB,YAAY;AAAA,IACzG,CAAC,OAAO;AAAA,EAAA;AAGZ,QAAM,iBAAiB;AAAA,IACnB,MAAM,QAAQ,OAAO,CAAC,MAAM,EAAE,UAAU,YAAA,EAAc,SAAS,QAAQ,CAAC;AAAA,IACxE,CAAC,OAAO;AAAA,EAAA;AAGZ,QAAM,eAAe;AAAA,IACjB,MAAM,QAAQ,OAAO,CAAC,MAAM,EAAE,UAAU,YAAA,EAAc,SAAS,UAAU,CAAC;AAAA,IAC1E,CAAC,OAAO;AAAA,EAAA;AAGZ,QAAM,iBAAiB;AAAA,IACnB,MAAM,QAAQ,OAAO,CAAC,MAAM,EAAE,UAAU,YAAA,EAAc,SAAS,YAAY,CAAC;AAAA,IAC5E,CAAC,OAAO;AAAA,EAAA;AAGZ,QAAM,cAAc;AAAA,IAChB,MACI,QAAQ;AAAA,MACJ,CAAC,MAAM,EAAE,UAAU,cAAc,SAAS,SAAS,KAAK,EAAE,UAAU,YAAA,EAAc,SAAS,UAAU;AAAA,IAAA;AAAA,IAE7G,CAAC,OAAO;AAAA,EAAA;AAGZ,SAAO;AAAA,IACH,OAAO;AAAA,MACH;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,IAEJ;AAAA,MACI;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EACJ;AAER;"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
const TeamContext = createContext(null);
|
|
3
|
+
function useTeam() {
|
|
4
|
+
const context = useContext(TeamContext);
|
|
5
|
+
if (!context) {
|
|
6
|
+
throw new Error("useTeam must be used within TeamProvider");
|
|
7
|
+
}
|
|
8
|
+
return context;
|
|
9
|
+
}
|
|
10
|
+
export {
|
|
11
|
+
TeamContext,
|
|
12
|
+
useTeam
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=team.context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"team.context.js","sources":["../../src/lib/providers/team.context.ts"],"sourcesContent":["\"use client\";\n\nimport { createContext, useContext } from \"react\";\nimport type { TeamContextValue } from \"./team.types\";\n\n// ============================================================================\n// Context\n// ============================================================================\n\nexport const TeamContext = createContext<TeamContextValue | null>(null);\n\n// ============================================================================\n// Hook\n// ============================================================================\n\n/**\n * Hook to access team context\n * Must be used within a TeamProvider\n */\nexport function useTeam(): TeamContextValue {\n const context = useContext(TeamContext);\n if (!context) {\n throw new Error(\"useTeam must be used within TeamProvider\");\n }\n return context;\n}\n\n"],"names":[],"mappings":";AASO,MAAM,cAAc,cAAuC,IAAI;AAU/D,SAAS,UAA4B;AACxC,QAAM,UAAU,WAAW,WAAW;AACtC,MAAI,CAAC,SAAS;AACV,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC9D;AACA,SAAO;AACX;"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect, useCallback, useMemo } from "react";
|
|
3
|
+
import { TeamContext } from "./team.context.js";
|
|
4
|
+
import { getFootballTeam, getFootballTeamSquad } from "../api/sportal365-sports/football/teams/index.js";
|
|
5
|
+
import { useSquadHelpers } from "./team/hooks/useSquadHelpers.js";
|
|
6
|
+
import { useMatchHelpers } from "./team/hooks/useMatchHelpers.js";
|
|
7
|
+
import { useFormStatsHelper } from "./team/hooks/useFormStats.js";
|
|
8
|
+
import { useDataLayerConfig } from "./fansunited-config.hooks.js";
|
|
9
|
+
function TeamProvider({
|
|
10
|
+
teamId,
|
|
11
|
+
team: initialTeam,
|
|
12
|
+
squad: initialSquad,
|
|
13
|
+
matches: initialMatches = [],
|
|
14
|
+
config,
|
|
15
|
+
autoRefresh = false,
|
|
16
|
+
refreshInterval = 6e4,
|
|
17
|
+
children
|
|
18
|
+
}) {
|
|
19
|
+
const dataLayerConfig = useDataLayerConfig();
|
|
20
|
+
const [team, setTeam] = useState(initialTeam);
|
|
21
|
+
const [squad, setSquad] = useState(initialSquad ?? null);
|
|
22
|
+
const [matches] = useState(initialMatches);
|
|
23
|
+
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
24
|
+
const [lastRefreshed, setLastRefreshed] = useState(void 0);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (team && squad) return;
|
|
27
|
+
const fetchInitialData = async () => {
|
|
28
|
+
try {
|
|
29
|
+
const [teamData, squadData] = await Promise.all([
|
|
30
|
+
!team ? getFootballTeam(teamId, void 0, dataLayerConfig) : Promise.resolve(team),
|
|
31
|
+
!squad ? getFootballTeamSquad(teamId, void 0, dataLayerConfig) : Promise.resolve(squad)
|
|
32
|
+
]);
|
|
33
|
+
if (!team) setTeam(teamData);
|
|
34
|
+
if (!squad) setSquad(squadData);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error("Failed to fetch initial team data:", error);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
fetchInitialData();
|
|
40
|
+
}, [dataLayerConfig]);
|
|
41
|
+
const refresh = useCallback(async () => {
|
|
42
|
+
setIsRefreshing(true);
|
|
43
|
+
try {
|
|
44
|
+
const [teamData, squadData] = await Promise.all([
|
|
45
|
+
getFootballTeam(teamId, void 0, dataLayerConfig),
|
|
46
|
+
getFootballTeamSquad(teamId, void 0, dataLayerConfig)
|
|
47
|
+
]);
|
|
48
|
+
setTeam(teamData);
|
|
49
|
+
setSquad(squadData);
|
|
50
|
+
setLastRefreshed(/* @__PURE__ */ new Date());
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error("Failed to refresh team data:", error);
|
|
53
|
+
} finally {
|
|
54
|
+
setIsRefreshing(false);
|
|
55
|
+
}
|
|
56
|
+
}, [teamId, dataLayerConfig]);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (!autoRefresh) return;
|
|
59
|
+
const interval = setInterval(refresh, refreshInterval);
|
|
60
|
+
return () => clearInterval(interval);
|
|
61
|
+
}, [autoRefresh, refreshInterval, refresh]);
|
|
62
|
+
const squadHelpers = useSquadHelpers(squad);
|
|
63
|
+
const effectiveTeamId = team?.id ?? teamId;
|
|
64
|
+
const matchHelpers = useMatchHelpers(effectiveTeamId, matches);
|
|
65
|
+
const { getFormStats } = useFormStatsHelper(effectiveTeamId, matches);
|
|
66
|
+
const contextValue = useMemo(() => {
|
|
67
|
+
return {
|
|
68
|
+
teamId,
|
|
69
|
+
team,
|
|
70
|
+
isRefreshing,
|
|
71
|
+
lastRefreshed,
|
|
72
|
+
squad,
|
|
73
|
+
matches,
|
|
74
|
+
config,
|
|
75
|
+
refresh,
|
|
76
|
+
getFormStats,
|
|
77
|
+
...squadHelpers,
|
|
78
|
+
...matchHelpers
|
|
79
|
+
};
|
|
80
|
+
}, [
|
|
81
|
+
teamId,
|
|
82
|
+
team,
|
|
83
|
+
isRefreshing,
|
|
84
|
+
lastRefreshed,
|
|
85
|
+
squad,
|
|
86
|
+
matches,
|
|
87
|
+
config,
|
|
88
|
+
refresh,
|
|
89
|
+
getFormStats,
|
|
90
|
+
squadHelpers,
|
|
91
|
+
matchHelpers
|
|
92
|
+
]);
|
|
93
|
+
return /* @__PURE__ */ jsx(TeamContext.Provider, { value: contextValue, children });
|
|
94
|
+
}
|
|
95
|
+
export {
|
|
96
|
+
TeamProvider
|
|
97
|
+
};
|
|
98
|
+
//# sourceMappingURL=team.provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"team.provider.js","sources":["../../src/lib/providers/team.provider.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, useEffect, useMemo, useCallback } from \"react\";\nimport type { FUSportsSquad, FUSportsCompetitor, FUSportsMatch } from \"../types/canonical\";\nimport { TeamContext } from \"./team.context\";\nimport type { TeamContextValue, TeamProviderProps } from \"./team.types\";\nimport { getFootballTeam, getFootballTeamSquad } from \"../api/sportal365-sports/football/teams\";\nimport { useSquadHelpers } from \"./team/hooks/useSquadHelpers\";\nimport { useMatchHelpers } from \"./team/hooks/useMatchHelpers\";\nimport { useFormStatsHelper } from \"./team/hooks/useFormStats\";\nimport { useDataLayerConfig } from \"./fansunited-config.hooks\";\n\n// ============================================================================\n// Provider Component\n// ============================================================================\n\nexport function TeamProvider({\n teamId,\n team: initialTeam,\n squad: initialSquad,\n matches: initialMatches = [],\n config,\n autoRefresh = false,\n refreshInterval = 60000,\n children,\n}: TeamProviderProps) {\n // Get data layer config from context\n const dataLayerConfig = useDataLayerConfig();\n\n // Core data state\n const [team, setTeam] = useState<FUSportsCompetitor | undefined>(initialTeam);\n const [squad, setSquad] = useState<FUSportsSquad | null>(initialSquad ?? null);\n const [matches] = useState<FUSportsMatch[]>(initialMatches);\n\n // Refresh state\n const [isRefreshing, setIsRefreshing] = useState(false);\n const [lastRefreshed, setLastRefreshed] = useState<Date | undefined>(undefined);\n\n // DON'T update state from props - this causes infinite loops in Next.js\n // The initial values are set once, and only the refresh() function updates them\n\n // Initial fetch for team and squad (only once if not provided)\n useEffect(() => {\n if (team && squad) return;\n\n const fetchInitialData = async () => {\n try {\n const [teamData, squadData] = await Promise.all([\n !team ? getFootballTeam(teamId, undefined, dataLayerConfig) : Promise.resolve(team),\n !squad ? getFootballTeamSquad(teamId, undefined, dataLayerConfig) : Promise.resolve(squad),\n ]);\n\n if (!team) setTeam(teamData);\n if (!squad) setSquad(squadData);\n } catch (error) {\n console.error(\"Failed to fetch initial team data:\", error);\n }\n };\n\n fetchInitialData();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [dataLayerConfig]);\n\n // Refresh function\n const refresh = useCallback(async () => {\n setIsRefreshing(true);\n try {\n const [teamData, squadData] = await Promise.all([\n getFootballTeam(teamId, undefined, dataLayerConfig),\n getFootballTeamSquad(teamId, undefined, dataLayerConfig),\n ]);\n\n setTeam(teamData);\n setSquad(squadData);\n setLastRefreshed(new Date());\n } catch (error) {\n console.error(\"Failed to refresh team data:\", error);\n } finally {\n setIsRefreshing(false);\n }\n }, [teamId, dataLayerConfig]);\n\n // Auto-refresh effect\n useEffect(() => {\n if (!autoRefresh) return;\n\n const interval = setInterval(refresh, refreshInterval);\n return () => clearInterval(interval);\n }, [autoRefresh, refreshInterval, refresh]);\n\n // Squad helpers\n const squadHelpers = useSquadHelpers(squad);\n\n // Match helpers - use team.id (numeric ID) if available, otherwise fall back to teamId\n const effectiveTeamId = team?.id ?? teamId;\n const matchHelpers = useMatchHelpers(effectiveTeamId, matches);\n\n // Form stats helper\n const { getFormStats } = useFormStatsHelper(effectiveTeamId, matches);\n\n // Memoize context value\n const contextValue = useMemo<TeamContextValue>(() => {\n return {\n teamId,\n team,\n isRefreshing,\n lastRefreshed,\n squad,\n matches,\n config,\n refresh,\n getFormStats,\n ...squadHelpers,\n ...matchHelpers,\n };\n }, [\n teamId,\n team,\n isRefreshing,\n lastRefreshed,\n squad,\n matches,\n config,\n refresh,\n getFormStats,\n squadHelpers,\n matchHelpers,\n ]);\n\n return <TeamContext.Provider value={contextValue}>{children}</TeamContext.Provider>;\n}\n"],"names":[],"mappings":";;;;;;;;AAgBO,SAAS,aAAa;AAAA,EACzB;AAAA,EACA,MAAM;AAAA,EACN,OAAO;AAAA,EACP,SAAS,iBAAiB,CAAA;AAAA,EAC1B;AAAA,EACA,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB;AACJ,GAAsB;AAElB,QAAM,kBAAkB,mBAAA;AAGxB,QAAM,CAAC,MAAM,OAAO,IAAI,SAAyC,WAAW;AAC5E,QAAM,CAAC,OAAO,QAAQ,IAAI,SAA+B,gBAAgB,IAAI;AAC7E,QAAM,CAAC,OAAO,IAAI,SAA0B,cAAc;AAG1D,QAAM,CAAC,cAAc,eAAe,IAAI,SAAS,KAAK;AACtD,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAA2B,MAAS;AAM9E,YAAU,MAAM;AACZ,QAAI,QAAQ,MAAO;AAEnB,UAAM,mBAAmB,YAAY;AACjC,UAAI;AACA,cAAM,CAAC,UAAU,SAAS,IAAI,MAAM,QAAQ,IAAI;AAAA,UAC5C,CAAC,OAAO,gBAAgB,QAAQ,QAAW,eAAe,IAAI,QAAQ,QAAQ,IAAI;AAAA,UAClF,CAAC,QAAQ,qBAAqB,QAAQ,QAAW,eAAe,IAAI,QAAQ,QAAQ,KAAK;AAAA,QAAA,CAC5F;AAED,YAAI,CAAC,KAAM,SAAQ,QAAQ;AAC3B,YAAI,CAAC,MAAO,UAAS,SAAS;AAAA,MAClC,SAAS,OAAO;AACZ,gBAAQ,MAAM,sCAAsC,KAAK;AAAA,MAC7D;AAAA,IACJ;AAEA,qBAAA;AAAA,EAEJ,GAAG,CAAC,eAAe,CAAC;AAGpB,QAAM,UAAU,YAAY,YAAY;AACpC,oBAAgB,IAAI;AACpB,QAAI;AACA,YAAM,CAAC,UAAU,SAAS,IAAI,MAAM,QAAQ,IAAI;AAAA,QAC5C,gBAAgB,QAAQ,QAAW,eAAe;AAAA,QAClD,qBAAqB,QAAQ,QAAW,eAAe;AAAA,MAAA,CAC1D;AAED,cAAQ,QAAQ;AAChB,eAAS,SAAS;AAClB,uBAAiB,oBAAI,MAAM;AAAA,IAC/B,SAAS,OAAO;AACZ,cAAQ,MAAM,gCAAgC,KAAK;AAAA,IACvD,UAAA;AACI,sBAAgB,KAAK;AAAA,IACzB;AAAA,EACJ,GAAG,CAAC,QAAQ,eAAe,CAAC;AAG5B,YAAU,MAAM;AACZ,QAAI,CAAC,YAAa;AAElB,UAAM,WAAW,YAAY,SAAS,eAAe;AACrD,WAAO,MAAM,cAAc,QAAQ;AAAA,EACvC,GAAG,CAAC,aAAa,iBAAiB,OAAO,CAAC;AAG1C,QAAM,eAAe,gBAAgB,KAAK;AAG1C,QAAM,kBAAkB,MAAM,MAAM;AACpC,QAAM,eAAe,gBAAgB,iBAAiB,OAAO;AAG7D,QAAM,EAAE,aAAA,IAAiB,mBAAmB,iBAAiB,OAAO;AAGpE,QAAM,eAAe,QAA0B,MAAM;AACjD,WAAO;AAAA,MACH;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAG;AAAA,MACH,GAAG;AAAA,IAAA;AAAA,EAEX,GAAG;AAAA,IACC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,CACH;AAED,6BAAQ,YAAY,UAAZ,EAAqB,OAAO,cAAe,UAAS;AAChE;"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
function calcPercentage(count, total) {
|
|
2
|
+
return total > 0 ? count / total * 100 : 0;
|
|
3
|
+
}
|
|
4
|
+
function calcAverage(sum, count) {
|
|
5
|
+
return count > 0 ? sum / count : 0;
|
|
6
|
+
}
|
|
7
|
+
function formatStatValue(value, isPercentage = false, decimals = 2) {
|
|
8
|
+
if (isPercentage) {
|
|
9
|
+
return `${Math.round(value)}%`;
|
|
10
|
+
}
|
|
11
|
+
if (Number.isInteger(value)) {
|
|
12
|
+
return value.toString();
|
|
13
|
+
}
|
|
14
|
+
return value.toFixed(decimals);
|
|
15
|
+
}
|
|
16
|
+
function formatWithAverage(total, matchesPlayed) {
|
|
17
|
+
const totalValue = typeof total === "string" ? parseFloat(total) : total;
|
|
18
|
+
if (isNaN(totalValue) || matchesPlayed === 0) return "0";
|
|
19
|
+
const average = (totalValue / matchesPlayed).toFixed(1);
|
|
20
|
+
return `${totalValue} (${average} avg)`;
|
|
21
|
+
}
|
|
22
|
+
function formatAsAverage(total, matchesPlayed) {
|
|
23
|
+
const totalValue = typeof total === "string" ? parseFloat(total) : total;
|
|
24
|
+
if (isNaN(totalValue) || matchesPlayed === 0) return "0.0";
|
|
25
|
+
return (totalValue / matchesPlayed).toFixed(1);
|
|
26
|
+
}
|
|
27
|
+
function formatPossessionPercentage(value) {
|
|
28
|
+
const numValue = parseFloat(value);
|
|
29
|
+
if (isNaN(numValue)) return "0%";
|
|
30
|
+
return `${Math.round(numValue)}%`;
|
|
31
|
+
}
|
|
32
|
+
function analyzeMatch(match, _teamId, isHome) {
|
|
33
|
+
if (match.status?.type !== "FINISHED") return null;
|
|
34
|
+
if (!match.score) return null;
|
|
35
|
+
const score1 = parseInt(match.score.competitorOne || "0");
|
|
36
|
+
const score2 = parseInt(match.score.competitorTwo || "0");
|
|
37
|
+
const goalsFor = isHome ? score1 : score2;
|
|
38
|
+
const goalsAgainst = isHome ? score2 : score1;
|
|
39
|
+
let halfTimeGoalsFor = null;
|
|
40
|
+
let halfTimeGoalsAgainst = null;
|
|
41
|
+
const halfTimeScore = match.score.breakdown?.halfTime;
|
|
42
|
+
if (halfTimeScore) {
|
|
43
|
+
const ht1 = parseInt(halfTimeScore.competitorOne || "0");
|
|
44
|
+
const ht2 = parseInt(halfTimeScore.competitorTwo || "0");
|
|
45
|
+
halfTimeGoalsFor = isHome ? ht1 : ht2;
|
|
46
|
+
halfTimeGoalsAgainst = isHome ? ht2 : ht1;
|
|
47
|
+
}
|
|
48
|
+
const stats = match.statistics;
|
|
49
|
+
let cornersFor = null;
|
|
50
|
+
let cornersAgainst = null;
|
|
51
|
+
if (stats) {
|
|
52
|
+
const cornerStat = stats.find?.((s) => s.type === "CORNERS");
|
|
53
|
+
if (cornerStat) {
|
|
54
|
+
cornersFor = isHome ? cornerStat.competitorOne : cornerStat.competitorTwo;
|
|
55
|
+
cornersAgainst = isHome ? cornerStat.competitorTwo : cornerStat.competitorOne;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
goalsFor,
|
|
60
|
+
goalsAgainst,
|
|
61
|
+
halfTimeGoalsFor,
|
|
62
|
+
halfTimeGoalsAgainst,
|
|
63
|
+
cornersFor,
|
|
64
|
+
cornersAgainst,
|
|
65
|
+
isWin: goalsFor > goalsAgainst,
|
|
66
|
+
isDraw: goalsFor === goalsAgainst,
|
|
67
|
+
isLoss: goalsFor < goalsAgainst
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function isTeamHome(match, teamId) {
|
|
71
|
+
return match.competitorOne.id === teamId;
|
|
72
|
+
}
|
|
73
|
+
function isTeamAway(match, teamId) {
|
|
74
|
+
return match.competitorTwo.id === teamId;
|
|
75
|
+
}
|
|
76
|
+
function isTeamInMatch(match, teamId) {
|
|
77
|
+
return isTeamHome(match, teamId) || isTeamAway(match, teamId);
|
|
78
|
+
}
|
|
79
|
+
function isMatchFinished(match) {
|
|
80
|
+
return match.status?.type === "FINISHED";
|
|
81
|
+
}
|
|
82
|
+
function getTeamScore(match, teamId) {
|
|
83
|
+
const isHome = isTeamHome(match, teamId);
|
|
84
|
+
return parseInt(isHome ? match.score?.competitorOne || "0" : match.score?.competitorTwo || "0");
|
|
85
|
+
}
|
|
86
|
+
function getOpponentScore(match, teamId) {
|
|
87
|
+
const isHome = isTeamHome(match, teamId);
|
|
88
|
+
return parseInt(isHome ? match.score?.competitorTwo || "0" : match.score?.competitorOne || "0");
|
|
89
|
+
}
|
|
90
|
+
export {
|
|
91
|
+
analyzeMatch,
|
|
92
|
+
calcAverage,
|
|
93
|
+
calcPercentage,
|
|
94
|
+
formatAsAverage,
|
|
95
|
+
formatPossessionPercentage,
|
|
96
|
+
formatStatValue,
|
|
97
|
+
formatWithAverage,
|
|
98
|
+
getOpponentScore,
|
|
99
|
+
getTeamScore,
|
|
100
|
+
isMatchFinished,
|
|
101
|
+
isTeamAway,
|
|
102
|
+
isTeamHome,
|
|
103
|
+
isTeamInMatch
|
|
104
|
+
};
|
|
105
|
+
//# sourceMappingURL=helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.js","sources":["../../../../src/lib/utilities/stats/core/helpers.ts"],"sourcesContent":["/**\n * Shared calculation helper functions.\n */\n\nimport type { FUSportsMatch } from \"../../../types/canonical\";\nimport type { MatchAnalysis } from \"./types\";\n\n/**\n * Calculate percentage from count and total.\n * @returns Percentage value (0-100)\n */\nexport function calcPercentage(count: number, total: number): number {\n return total > 0 ? (count / total) * 100 : 0;\n}\n\n/**\n * Calculate average from sum and count.\n */\nexport function calcAverage(sum: number, count: number): number {\n return count > 0 ? sum / count : 0;\n}\n\n/**\n * Format a number as a display value.\n * @param value - The numeric value to format\n * @param isPercentage - If true, appends % symbol\n * @param decimals - Number of decimal places (default: 2)\n */\nexport function formatStatValue(value: number, isPercentage = false, decimals = 2): string {\n if (isPercentage) {\n return `${Math.round(value)}%`;\n }\n if (Number.isInteger(value)) {\n return value.toString();\n }\n return value.toFixed(decimals);\n}\n\n/**\n * Format stats with total and average per match.\n * @example formatWithAverage(10, 5) => \"10 (2.0 avg)\"\n */\nexport function formatWithAverage(total: string | number, matchesPlayed: number): string {\n const totalValue = typeof total === \"string\" ? parseFloat(total) : total;\n if (isNaN(totalValue) || matchesPlayed === 0) return \"0\";\n const average = (totalValue / matchesPlayed).toFixed(1);\n return `${totalValue} (${average} avg)`;\n}\n\n/**\n * Format stats as average only.\n * @example formatAsAverage(10, 5) => \"2.0\"\n */\nexport function formatAsAverage(total: string | number, matchesPlayed: number): string {\n const totalValue = typeof total === \"string\" ? parseFloat(total) : total;\n if (isNaN(totalValue) || matchesPlayed === 0) return \"0.0\";\n return (totalValue / matchesPlayed).toFixed(1);\n}\n\n/**\n * Format possession percentage with % symbol.\n */\nexport function formatPossessionPercentage(value: string): string {\n const numValue = parseFloat(value);\n if (isNaN(numValue)) return \"0%\";\n return `${Math.round(numValue)}%`;\n}\n\n/**\n * Analyze a match from a team's perspective.\n * @returns MatchAnalysis or null if match is not finished\n */\nexport function analyzeMatch(match: FUSportsMatch, _teamId: string, isHome: boolean): MatchAnalysis | null {\n if (match.status?.type !== \"FINISHED\") return null;\n if (!match.score) return null;\n\n const score1 = parseInt(match.score.competitorOne || \"0\");\n const score2 = parseInt(match.score.competitorTwo || \"0\");\n\n const goalsFor = isHome ? score1 : score2;\n const goalsAgainst = isHome ? score2 : score1;\n\n // Try to get half-time scores from score breakdown\n let halfTimeGoalsFor: number | null = null;\n let halfTimeGoalsAgainst: number | null = null;\n\n const halfTimeScore = match.score.breakdown?.halfTime;\n if (halfTimeScore) {\n const ht1 = parseInt(halfTimeScore.competitorOne || \"0\");\n const ht2 = parseInt(halfTimeScore.competitorTwo || \"0\");\n halfTimeGoalsFor = isHome ? ht1 : ht2;\n halfTimeGoalsAgainst = isHome ? ht2 : ht1;\n }\n\n // Try to get corner stats from match statistics\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const stats = (match as any).statistics;\n let cornersFor: number | null = null;\n let cornersAgainst: number | null = null;\n\n if (stats) {\n const cornerStat = stats.find?.((s: { type?: string }) => s.type === \"CORNERS\");\n if (cornerStat) {\n cornersFor = isHome ? cornerStat.competitorOne : cornerStat.competitorTwo;\n cornersAgainst = isHome ? cornerStat.competitorTwo : cornerStat.competitorOne;\n }\n }\n\n return {\n goalsFor,\n goalsAgainst,\n halfTimeGoalsFor,\n halfTimeGoalsAgainst,\n cornersFor,\n cornersAgainst,\n isWin: goalsFor > goalsAgainst,\n isDraw: goalsFor === goalsAgainst,\n isLoss: goalsFor < goalsAgainst,\n };\n}\n\n/**\n * Check if a team is the home team in a match.\n */\nexport function isTeamHome(match: FUSportsMatch, teamId: string): boolean {\n return match.competitorOne.id === teamId;\n}\n\n/**\n * Check if a team is the away team in a match.\n */\nexport function isTeamAway(match: FUSportsMatch, teamId: string): boolean {\n return match.competitorTwo.id === teamId;\n}\n\n/**\n * Check if a team participates in a match.\n */\nexport function isTeamInMatch(match: FUSportsMatch, teamId: string): boolean {\n return isTeamHome(match, teamId) || isTeamAway(match, teamId);\n}\n\n/**\n * Check if a match is finished.\n */\nexport function isMatchFinished(match: FUSportsMatch): boolean {\n return match.status?.type === \"FINISHED\";\n}\n\n/**\n * Get team's score from a match.\n */\nexport function getTeamScore(match: FUSportsMatch, teamId: string): number {\n const isHome = isTeamHome(match, teamId);\n return parseInt(isHome ? match.score?.competitorOne || \"0\" : match.score?.competitorTwo || \"0\");\n}\n\n/**\n * Get opponent's score from a match.\n */\nexport function getOpponentScore(match: FUSportsMatch, teamId: string): number {\n const isHome = isTeamHome(match, teamId);\n return parseInt(isHome ? match.score?.competitorTwo || \"0\" : match.score?.competitorOne || \"0\");\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sources":["../../../../src/lib/utilities/stats/core/types.ts"],"sourcesContent":["/**\n * Shared types for statistics calculations.\n */\n\nimport type { FUSportsMatch } from \"../../../types/canonical\";\n\n/**\n * Analyzed match data from a team's perspective.\n */\nexport interface MatchAnalysis {\n goalsFor: number;\n goalsAgainst: number;\n halfTimeGoalsFor: number | null;\n halfTimeGoalsAgainst: number | null;\n cornersFor: number | null;\n cornersAgainst: number | null;\n isWin: boolean;\n isDraw: boolean;\n isLoss: boolean;\n}\n\n/**\n * Match result from a team's perspective.\n */\nexport interface MatchResult {\n score: string;\n result: \"W\" | \"D\" | \"L\";\n isHome: boolean;\n}\n\n/**\n * Filter function type for match filtering.\n */\nexport type MatchFilter = (match: FUSportsMatch, teamId: string, isHome: boolean) => boolean;\n\n/**\n * Venue filter type.\n */\nexport type VenueFilter = \"home\" | \"away\";\n\n/**\n * Goal event types that count as a goal.\n */\nexport const GOAL_EVENT_TYPES = [\"GOAL\", \"PENALTY_GOAL\", \"OWN_GOAL\"] as const;\n"],"names":[],"mappings":"AA2CO,MAAM,mBAAmB,CAAC,QAAQ,gBAAgB,UAAU;"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
function calculateH2HStats(matches, homeTeamId, awayTeamId) {
|
|
2
|
+
const h2hMatches = matches.filter((m) => {
|
|
3
|
+
if (m.status?.type !== "FINISHED" || !m.score) return false;
|
|
4
|
+
const hasHomeTeam = m.competitorOne.id === homeTeamId || m.competitorTwo.id === homeTeamId;
|
|
5
|
+
const hasAwayTeam = m.competitorOne.id === awayTeamId || m.competitorTwo.id === awayTeamId;
|
|
6
|
+
return hasHomeTeam && hasAwayTeam;
|
|
7
|
+
});
|
|
8
|
+
let homeTeamWins = 0;
|
|
9
|
+
let awayTeamWins = 0;
|
|
10
|
+
let draws = 0;
|
|
11
|
+
let homeTeamGoals = 0;
|
|
12
|
+
let awayTeamGoals = 0;
|
|
13
|
+
h2hMatches.forEach((match) => {
|
|
14
|
+
const score1 = parseInt(match.score?.competitorOne || "0");
|
|
15
|
+
const score2 = parseInt(match.score?.competitorTwo || "0");
|
|
16
|
+
const isHomeTeamCompetitorOne = match.competitorOne.id === homeTeamId;
|
|
17
|
+
const homeGoals = isHomeTeamCompetitorOne ? score1 : score2;
|
|
18
|
+
const awayGoals = isHomeTeamCompetitorOne ? score2 : score1;
|
|
19
|
+
homeTeamGoals += homeGoals;
|
|
20
|
+
awayTeamGoals += awayGoals;
|
|
21
|
+
if (homeGoals > awayGoals) {
|
|
22
|
+
homeTeamWins++;
|
|
23
|
+
} else if (awayGoals > homeGoals) {
|
|
24
|
+
awayTeamWins++;
|
|
25
|
+
} else {
|
|
26
|
+
draws++;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
const totalMatches = h2hMatches.length;
|
|
30
|
+
return {
|
|
31
|
+
totalMatches,
|
|
32
|
+
homeTeamWins,
|
|
33
|
+
awayTeamWins,
|
|
34
|
+
draws,
|
|
35
|
+
homeTeamGoals,
|
|
36
|
+
awayTeamGoals,
|
|
37
|
+
homeTeamWinPercentage: totalMatches > 0 ? homeTeamWins / totalMatches * 100 : 0,
|
|
38
|
+
awayTeamWinPercentage: totalMatches > 0 ? awayTeamWins / totalMatches * 100 : 0,
|
|
39
|
+
drawPercentage: totalMatches > 0 ? draws / totalMatches * 100 : 0,
|
|
40
|
+
homeTeamAvgGoals: totalMatches > 0 ? homeTeamGoals / totalMatches : 0,
|
|
41
|
+
awayTeamAvgGoals: totalMatches > 0 ? awayTeamGoals / totalMatches : 0
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function getRecentH2HMeetings(matches, homeTeamId, awayTeamId, limit = 5) {
|
|
45
|
+
const h2hMatches = matches.filter((m) => {
|
|
46
|
+
if (m.status?.type !== "FINISHED" || !m.score) return false;
|
|
47
|
+
const hasHomeTeam = m.competitorOne.id === homeTeamId || m.competitorTwo.id === homeTeamId;
|
|
48
|
+
const hasAwayTeam = m.competitorOne.id === awayTeamId || m.competitorTwo.id === awayTeamId;
|
|
49
|
+
return hasHomeTeam && hasAwayTeam;
|
|
50
|
+
}).sort((a, b) => new Date(b.timing.kickoffTime).getTime() - new Date(a.timing.kickoffTime).getTime()).slice(0, limit);
|
|
51
|
+
return h2hMatches.map((match) => {
|
|
52
|
+
const score1 = parseInt(match.score?.competitorOne || "0");
|
|
53
|
+
const score2 = parseInt(match.score?.competitorTwo || "0");
|
|
54
|
+
const isHomeTeamCompetitorOne = match.competitorOne.id === homeTeamId;
|
|
55
|
+
const homeGoals = isHomeTeamCompetitorOne ? score1 : score2;
|
|
56
|
+
const awayGoals = isHomeTeamCompetitorOne ? score2 : score1;
|
|
57
|
+
let winner;
|
|
58
|
+
if (homeGoals > awayGoals) winner = "home";
|
|
59
|
+
else if (awayGoals > homeGoals) winner = "away";
|
|
60
|
+
else winner = "draw";
|
|
61
|
+
return {
|
|
62
|
+
id: match.id,
|
|
63
|
+
date: new Date(match.timing.kickoffTime).toLocaleDateString("en-GB", {
|
|
64
|
+
day: "numeric",
|
|
65
|
+
month: "short",
|
|
66
|
+
year: "numeric"
|
|
67
|
+
}),
|
|
68
|
+
homeTeamName: match.competitorOne.name,
|
|
69
|
+
awayTeamName: match.competitorTwo.name,
|
|
70
|
+
homeTeamLogo: match.competitorOne.assets?.logo,
|
|
71
|
+
awayTeamLogo: match.competitorTwo.assets?.logo,
|
|
72
|
+
homeScore: score1,
|
|
73
|
+
awayScore: score2,
|
|
74
|
+
venue: match.venue?.name,
|
|
75
|
+
winner
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
export {
|
|
80
|
+
calculateH2HStats,
|
|
81
|
+
getRecentH2HMeetings
|
|
82
|
+
};
|
|
83
|
+
//# sourceMappingURL=headToHead.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"headToHead.js","sources":["../../../../src/lib/utilities/stats/match/headToHead.ts"],"sourcesContent":["/**\n * Head-to-head statistics calculations between two teams.\n *\n * Note: To fetch H2H matches from the API, use `getFootballMatches` with both team IDs:\n *\n * ```typescript\n * import { getFootballMatches } from 'fansunited-data-layer';\n *\n * const h2hMatches = await getFootballMatches({\n * teamIds: [homeTeamId, awayTeamId],\n * teamIdsOperator: 'AND',\n * statusTypes: ['FINISHED'],\n * sortDirection: 'desc',\n * limit: 50,\n * offset: 0\n * });\n *\n * const stats = calculateH2HStats(h2hMatches, homeTeamId, awayTeamId);\n * ```\n */\n\nimport type { FUSportsMatch } from \"../../../types/canonical\";\n\nexport interface H2HStats {\n totalMatches: number;\n homeTeamWins: number;\n awayTeamWins: number;\n draws: number;\n homeTeamGoals: number;\n awayTeamGoals: number;\n homeTeamWinPercentage: number;\n awayTeamWinPercentage: number;\n drawPercentage: number;\n homeTeamAvgGoals: number;\n awayTeamAvgGoals: number;\n}\n\nexport interface H2HMatch {\n id: string;\n date: string;\n homeTeamName: string;\n awayTeamName: string;\n homeTeamLogo?: string;\n awayTeamLogo?: string;\n homeScore: number;\n awayScore: number;\n venue?: string;\n /** Which team won from perspective of the two teams we're comparing */\n winner: \"home\" | \"away\" | \"draw\";\n}\n\n/**\n * Calculate H2H statistics between two teams from an array of matches.\n *\n * The matches array should contain only matches between these two teams.\n * Use `getFootballMatches` with `teamIds` and `teamIdsOperator: 'AND'` to fetch H2H matches.\n *\n * @param matches - Array of matches between the two teams (fetch using getFootballMatches)\n * @param homeTeamId - ID of the home team in the current context\n * @param awayTeamId - ID of the away team in the current context\n * @returns H2H statistics\n */\nexport function calculateH2HStats(matches: FUSportsMatch[], homeTeamId: string, awayTeamId: string): H2HStats {\n // Filter for finished matches between the two teams\n const h2hMatches = matches.filter((m) => {\n if (m.status?.type !== \"FINISHED\" || !m.score) return false;\n // Ensure match is between the two specified teams\n const hasHomeTeam = m.competitorOne.id === homeTeamId || m.competitorTwo.id === homeTeamId;\n const hasAwayTeam = m.competitorOne.id === awayTeamId || m.competitorTwo.id === awayTeamId;\n return hasHomeTeam && hasAwayTeam;\n });\n\n let homeTeamWins = 0;\n let awayTeamWins = 0;\n let draws = 0;\n let homeTeamGoals = 0;\n let awayTeamGoals = 0;\n\n h2hMatches.forEach((match) => {\n const score1 = parseInt(match.score?.competitorOne || \"0\");\n const score2 = parseInt(match.score?.competitorTwo || \"0\");\n\n // Determine which competitor is our \"home\" team\n const isHomeTeamCompetitorOne = match.competitorOne.id === homeTeamId;\n\n const homeGoals = isHomeTeamCompetitorOne ? score1 : score2;\n const awayGoals = isHomeTeamCompetitorOne ? score2 : score1;\n\n homeTeamGoals += homeGoals;\n awayTeamGoals += awayGoals;\n\n if (homeGoals > awayGoals) {\n homeTeamWins++;\n } else if (awayGoals > homeGoals) {\n awayTeamWins++;\n } else {\n draws++;\n }\n });\n\n const totalMatches = h2hMatches.length;\n\n return {\n totalMatches,\n homeTeamWins,\n awayTeamWins,\n draws,\n homeTeamGoals,\n awayTeamGoals,\n homeTeamWinPercentage: totalMatches > 0 ? (homeTeamWins / totalMatches) * 100 : 0,\n awayTeamWinPercentage: totalMatches > 0 ? (awayTeamWins / totalMatches) * 100 : 0,\n drawPercentage: totalMatches > 0 ? (draws / totalMatches) * 100 : 0,\n homeTeamAvgGoals: totalMatches > 0 ? homeTeamGoals / totalMatches : 0,\n awayTeamAvgGoals: totalMatches > 0 ? awayTeamGoals / totalMatches : 0,\n };\n}\n\n/**\n * Get formatted list of recent H2H meetings.\n *\n * The matches array should contain only matches between these two teams.\n * Use `getFootballMatches` with `teamIds` and `teamIdsOperator: 'AND'` to fetch H2H matches.\n *\n * @param matches - Array of matches between the two teams (fetch using getFootballMatches)\n * @param homeTeamId - ID of the home team in the current context\n * @param awayTeamId - ID of the away team in the current context (used for determining winner perspective)\n * @param limit - Maximum number of recent meetings to return\n * @returns Array of formatted H2H matches\n */\nexport function getRecentH2HMeetings(\n matches: FUSportsMatch[],\n homeTeamId: string,\n awayTeamId: string,\n limit: number = 5\n): H2HMatch[] {\n const h2hMatches = matches\n .filter((m) => {\n if (m.status?.type !== \"FINISHED\" || !m.score) return false;\n // Ensure match is between the two specified teams\n const hasHomeTeam = m.competitorOne.id === homeTeamId || m.competitorTwo.id === homeTeamId;\n const hasAwayTeam = m.competitorOne.id === awayTeamId || m.competitorTwo.id === awayTeamId;\n return hasHomeTeam && hasAwayTeam;\n })\n .sort((a, b) => new Date(b.timing.kickoffTime).getTime() - new Date(a.timing.kickoffTime).getTime())\n .slice(0, limit);\n\n return h2hMatches.map((match) => {\n const score1 = parseInt(match.score?.competitorOne || \"0\");\n const score2 = parseInt(match.score?.competitorTwo || \"0\");\n\n // Determine winner from perspective of homeTeamId\n const isHomeTeamCompetitorOne = match.competitorOne.id === homeTeamId;\n const homeGoals = isHomeTeamCompetitorOne ? score1 : score2;\n const awayGoals = isHomeTeamCompetitorOne ? score2 : score1;\n\n let winner: \"home\" | \"away\" | \"draw\";\n if (homeGoals > awayGoals) winner = \"home\";\n else if (awayGoals > homeGoals) winner = \"away\";\n else winner = \"draw\";\n\n return {\n id: match.id,\n date: new Date(match.timing.kickoffTime).toLocaleDateString(\"en-GB\", {\n day: \"numeric\",\n month: \"short\",\n year: \"numeric\",\n }),\n homeTeamName: match.competitorOne.name,\n awayTeamName: match.competitorTwo.name,\n homeTeamLogo: match.competitorOne.assets?.logo,\n awayTeamLogo: match.competitorTwo.assets?.logo,\n homeScore: score1,\n awayScore: score2,\n venue: match.venue?.name,\n winner,\n };\n });\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -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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"}
|