rustat-python-api 0.4.11__py3-none-any.whl

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.
@@ -0,0 +1,4 @@
1
+ from .parser import RuStatParser
2
+ from .models_api import DynamoLab
3
+
4
+ __all__ = ['RuStatParser', 'DynamoLab']
@@ -0,0 +1,37 @@
1
+ columns = [
2
+ 'player_name', 'team_name', 'half', 'second', 'action_id', 'action_name',
3
+ 'position_name', 'possession_number', 'pos_x', 'pos_y', 'pos_dest_x', 'pos_dest_y',
4
+ 'player_id', 'number', 'team_id', 'standart_name', 'possession_time',
5
+ 'opponent_id', 'opponent_name', 'opponent_team_id', 'opponent_team_name',
6
+ 'opponent_position_name', 'zone_name', 'zone_dest_name', 'len',
7
+ 'possession_team_id', 'possession_team_name', 'possession_name',
8
+ 'attack_status_name', 'attack_type_name', 'attack_flang_name',
9
+ 'attack_team_id', 'attack_team_name', 'attack_number',
10
+ 'body_name', 'gate_x', 'gate_y', 'assistant_id',
11
+ 'assistant_name', 'shot_type', 'touches', 'xg',
12
+ 'shot_handling', 'match_id', 'receiver_id', 'receiver_name'
13
+ ]
14
+
15
+ numeric_columns = [
16
+ 'id', 'number', 'player_id', 'team_id', 'half', 'second',
17
+ 'pos_x', 'pos_y', 'pos_dest_x', 'pos_dest_y', 'len', 'possession_id', 'possession_team_id',
18
+ 'opponent_id', 'opponent_team_id', 'zone_id', 'zone_dest_id',
19
+ 'possession_number', 'attack_status_id', 'attack_team_id', 'assistant_id', 'touches', 'xg'
20
+ ]
21
+
22
+ id2type = {
23
+ 1: 'pass', 2: 'duel', 3: 'foul',
24
+ 4: 'shot', 5: 'free kick', 6: 'interception',
25
+ 7: 'rebound', 8: 'goal', 9: 'clearance',
26
+ 10: 'bad ball control', 11: 'control', 12: 'attack',
27
+ 13: 'keeper', 14: 'substitution', 15: 'formation',
28
+ 16: 'player position', 17: 'ball off', 18: 'match status',
29
+ 19: 'mistake', 20: 'translation problem', 21: 'carry',
30
+ 22: 'receive', 23: 'goal attack involvement', 24: 'rating',
31
+ 25: 'average position', 26: 'cross', 27: 'ball out',
32
+ 28: 'other', 29: 'video', 30: 'bad mistake',
33
+ 31: 'bad keeper mistake', 32: 'goal moment', 33: 'team pressing',
34
+ 34: 'line up', 35: 'sync', 36: 'referee',
35
+ 37: 'insurance', 38: 'injury',
36
+ 128: 'staff', 161: 'sub player'
37
+ }
@@ -0,0 +1,58 @@
1
+ import requests
2
+ import numpy as np
3
+ import pandas as pd
4
+
5
+
6
+ class DynamoLab:
7
+ def __init__(self, host: str = "http://localhost:8000/"):
8
+ self.host = host
9
+
10
+ def get_active_models(self):
11
+ url = self.host + "models"
12
+ response = requests.get(url)
13
+ return response.json()
14
+
15
+ def run_model(
16
+ self,
17
+ model: str,
18
+ data: pd.DataFrame,
19
+ return_type: str = "json",
20
+ inplace: bool = False,
21
+ return_df: bool = False,
22
+ inplace_column: str = None
23
+ ):
24
+ """
25
+ model: str
26
+ The name of the model to run
27
+ return_type: str
28
+ One of: "json", "list", "numpy"
29
+ """
30
+
31
+ url = self.host + "predict"
32
+ csv_string = data.to_csv(index=False)
33
+
34
+ body = {
35
+ "model": model,
36
+ "data": csv_string,
37
+ }
38
+
39
+ response = requests.post(url, json=body)
40
+
41
+ if return_df or inplace:
42
+ if inplace_column is None:
43
+ raise ValueError("inplace_column must be specified if inplace is True")
44
+ if inplace:
45
+ data[inplace_column] = np.array(response.json()["prediction"])
46
+ else:
47
+ df = data.copy()
48
+ df[inplace_column] = np.array(response.json()["prediction"])
49
+ return df
50
+ else:
51
+ if return_type == "json":
52
+ return response.json()
53
+ elif return_type == "list":
54
+ return response.json()["prediction"]
55
+ elif return_type == "numpy":
56
+ return np.array(response.json()["prediction"])
57
+ else:
58
+ raise ValueError("return_type must be one of: json, list, numpy")
@@ -0,0 +1,226 @@
1
+ import requests
2
+ import pandas as pd
3
+ from collections import defaultdict
4
+ from tqdm import tqdm
5
+ import time
6
+
7
+ from .urls import URLs
8
+ from .config import numeric_columns
9
+ from .processing import processing
10
+
11
+
12
+ class RuStatParser:
13
+ def __init__(
14
+ self,
15
+ user: str,
16
+ password: str,
17
+ urls: dict = URLs,
18
+ sleep: int = -1
19
+ ):
20
+ self.user = user
21
+ self.password = password
22
+ self.urls = urls
23
+ self.sleep = sleep
24
+
25
+ self.cached_info = {}
26
+
27
+ def resp2data(self, query: str) -> dict:
28
+
29
+ if self.sleep > 0:
30
+ time.sleep(self.sleep)
31
+
32
+ response = requests.get(query)
33
+ return response.json()
34
+
35
+ def get_rpl_info(self):
36
+ for season_id in tqdm(range(1, 36)):
37
+ data = self.resp2data(
38
+ self.urls["tournament_teams"].format(
39
+ user=self.user,
40
+ password=self.password,
41
+ season_id=season_id
42
+ )
43
+ )
44
+
45
+ if data:
46
+ first_team_id = data["data"]["row"][0]["id"]
47
+ first_team_schedule = self.resp2data(
48
+ self.urls["schedule"].format(
49
+ user=self.user,
50
+ password=self.password,
51
+ team_id=first_team_id,
52
+ season_id=season_id
53
+ )
54
+ )
55
+
56
+ if first_team_schedule:
57
+ last_match = first_team_schedule["data"]["row"][0]
58
+ season_name = f'{last_match["tournament_name"]} {last_match["season_name"]}'
59
+ else:
60
+ season_name = ""
61
+
62
+ self.cached_info[season_id] = {
63
+ "season_name": season_name,
64
+ "season_teams": data["data"]["row"]
65
+ }
66
+
67
+ return self.cached_info
68
+
69
+ def get_schedule(self, team_id: str, season_id: str) -> dict:
70
+ data = self.resp2data(
71
+ self.urls["schedule"].format(
72
+ user=self.user,
73
+ password=self.password,
74
+ team_id=team_id,
75
+ season_id=season_id
76
+ )
77
+ )
78
+
79
+ if not data:
80
+ return {}
81
+
82
+ return {
83
+ int(row["id"]): {
84
+ "match_date": row["match_date"],
85
+ "team1_id": int(row["team1_id"]),
86
+ "team2_id": int(row["team2_id"]),
87
+ "team1_name": row["team1_name"],
88
+ "team2_name": row["team2_name"],
89
+ "round_name": (row["round_name"] if "round_name" in row else None),
90
+ "tournament_name": (row["tournament_name"] if "tournament_name" in row else None),
91
+ "season_name": (row["season_name"] if "season_name" in row else None)
92
+ }
93
+ for row in data["data"]["row"]
94
+ }
95
+
96
+ def get_events(
97
+ self,
98
+ match_id: int,
99
+ process: bool = True,
100
+ return_subs: bool = True
101
+ ) -> pd.DataFrame | None | tuple[pd.DataFrame, pd.DataFrame]:
102
+ data = self.resp2data(
103
+ self.urls["events"].format(
104
+ user=self.user,
105
+ password=self.password,
106
+ match_id=match_id
107
+ )
108
+ )
109
+
110
+ if not data:
111
+ return None
112
+
113
+ df = pd.json_normalize(data["data"]["row"])
114
+
115
+ current_numeric_columns = [column for column in numeric_columns if column in df.columns]
116
+ df[current_numeric_columns] = df[current_numeric_columns].apply(pd.to_numeric, errors='coerce')
117
+
118
+ if process:
119
+ df['match_id'] = match_id
120
+
121
+ if return_subs:
122
+ subs = df[df['action_id'] == '14000'][[
123
+ 'match_id', 'half', 'second',
124
+ 'team_id', 'team_name',
125
+ 'opponent_id', 'opponent_name',
126
+ 'player_id', 'player_name'
127
+ ]].rename(columns={
128
+ 'player_id': 'player_id_out',
129
+ 'opponent_id': 'player_id_in',
130
+ 'player_name': 'player_name_out',
131
+ 'opponent_name': 'player_name_in'
132
+ })
133
+
134
+ df = processing(df)
135
+
136
+ return (df, subs) if return_subs else df
137
+
138
+ def get_tracking(self, match_id: int) -> pd.DataFrame | None:
139
+ data = self.resp2data(
140
+ self.urls["tracking"].format(
141
+ user=self.user,
142
+ password=self.password,
143
+ match_id=match_id
144
+ )
145
+ )
146
+
147
+ if not data:
148
+ return None
149
+
150
+ data = data["data"]["team"]
151
+ df = pd.DataFrame(columns=["half", "second", "pos_x", "pos_y", "team_id", "player_id", "player_name", "side_1h"])
152
+
153
+ for team_data in tqdm(data):
154
+ team_id = team_data["id"]
155
+ side_1h = team_data["gate_position_half_1"]
156
+
157
+ for player_data in team_data["player"]:
158
+ player_id = player_data["id"]
159
+ player_name = player_data["name"]
160
+
161
+ cur_df = pd.json_normalize(player_data["row"])
162
+ cur_df = cur_df.apply(pd.to_numeric, errors='coerce')
163
+ cur_df["team_id"] = team_id
164
+ cur_df["player_id"] = player_id
165
+ cur_df["player_name"] = player_name
166
+ cur_df["side_1h"] = side_1h
167
+
168
+ df = pd.concat([df, cur_df], ignore_index=True)
169
+
170
+ return df.sort_values(by=["second", "team_id", "player_id"])
171
+
172
+ def get_match_stats(self, match_id: int) -> dict:
173
+ data = self.resp2data(
174
+ self.urls["match_stats"].format(
175
+ user=self.user,
176
+ password=self.password,
177
+ match_id=match_id
178
+ )
179
+ )
180
+
181
+ if not data:
182
+ return {}
183
+
184
+ stats = defaultdict(dict)
185
+
186
+ for row in data['data']['row']:
187
+ team_id = int(row['team_id'])
188
+ param_name = row['param_name']
189
+
190
+ param_value = float(row['value'])
191
+
192
+ stats[param_name][team_id] = param_value
193
+
194
+ return stats
195
+
196
+ def get_players_match_stats(self, match_id: int) -> dict:
197
+ data = self.resp2data(
198
+ self.urls["player_match_stats"].format(
199
+ user=self.user,
200
+ password=self.password,
201
+ match_id=match_id
202
+ )
203
+ )
204
+
205
+ if not data:
206
+ return {}
207
+
208
+ return data['data']['team']
209
+
210
+ def get_players_minutes_in_match(self, match_id: int) -> dict:
211
+ data = self.get_players_match_stats(match_id)
212
+
213
+ if not data:
214
+ return {}
215
+
216
+ players_minutes = {}
217
+
218
+ for team_data in data:
219
+ for player_data in team_data['player']:
220
+ player_id = int(player_data['id'])
221
+
222
+ minutes = [float(metric['value']) for metric in player_data['param'] if metric['id'] == '288'][0]
223
+
224
+ players_minutes[player_id] = minutes
225
+
226
+ return players_minutes
@@ -0,0 +1,89 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+
4
+ from .config import columns, id2type
5
+
6
+
7
+ def process_list(x: pd.Series):
8
+ lst = x.dropna().unique().tolist()
9
+ if len(lst) == 1:
10
+ return lst[0]
11
+ elif len(lst) == 0:
12
+ return np.nan
13
+ else:
14
+ return lst
15
+
16
+
17
+ def gluing(df: pd.DataFrame) -> pd.DataFrame:
18
+ cols = ['player_id', 'half', 'second', 'pos_x', 'pos_y']
19
+
20
+ df_gb = df.groupby(cols).agg(process_list).reset_index()
21
+ df_gb['possession_number'] = df_gb['possession_number'].apply(
22
+ lambda x: max(x) if isinstance(x, list) else x
23
+ )
24
+ df_gb['pos_dest_x'] = df_gb['pos_dest_x'].apply(
25
+ lambda x: x[0] if isinstance(x, list) else x
26
+ )
27
+ df_gb['pos_dest_y'] = df_gb['pos_dest_y'].apply(
28
+ lambda x: x[0] if isinstance(x, list) else x
29
+ )
30
+ df_gb['pos_dest_nan'] = (df_gb['pos_dest_x'].isna() & df_gb['pos_dest_y'].isna()).astype(int)
31
+ df_gb = df_gb.sort_values(by=['half', 'second', 'possession_number', 'pos_dest_nan']).reset_index(drop=True)
32
+ return df_gb
33
+
34
+
35
+ def add_reciever(glued_df: pd.DataFrame) -> pd.DataFrame:
36
+ df = glued_df.copy()
37
+ df['receiver_id'] = df['player_id'].shift(-1)
38
+ df['receiver_name'] = df['player_name'].shift(-1)
39
+
40
+ mask = (
41
+ (df['action_name'] == 'Ball receiving')
42
+ & (df['pos_x'] == df['pos_dest_x'].shift(1))
43
+ & (df['pos_y'] == df['pos_dest_y'].shift(1))
44
+ & (df['team_id'] == df['team_id'].shift(1))
45
+ & (df['player_id'] != df['player_id'].shift(1))
46
+ & (df['possession_number'] == df['possession_number'].shift(1))
47
+ )
48
+
49
+ idx = df[mask].index
50
+ remaining_idx = df.drop(idx-1).index
51
+
52
+ df.loc[remaining_idx, 'receiver_id'] = np.nan
53
+ df.loc[remaining_idx, 'receiver_name'] = np.nan
54
+
55
+ df = df[df['action_name'] != 'Ball receiving'].reset_index(drop=True)
56
+
57
+ return df
58
+
59
+
60
+ def filter_data(df: pd.DataFrame) -> pd.DataFrame:
61
+ for column in columns:
62
+ if column not in df.columns:
63
+ df[column] = np.nan
64
+
65
+ return df[(~df['possession_number'].isna()) | (df['second'] != 0)][columns].reset_index(drop=True)
66
+
67
+
68
+ def tagging(df: pd.DataFrame) -> pd.DataFrame:
69
+ df = df.rename(columns={'action_name': 'sub_tags', 'action_id': 'sub_tags_ids'})
70
+ df['sub_tags'] = df['sub_tags'].apply(lambda x: x if isinstance(x, list) else [x])
71
+ df['sub_tags_ids'] = df['sub_tags_ids'].apply(
72
+ lambda x:
73
+ list(set([int(t) // 1000 for t in x]))
74
+ if isinstance(x, list)
75
+ else [int(x) // 1000]
76
+ )
77
+ df['sub_tags_ids'] = df['sub_tags_ids'].apply(lambda x: [id2type[t] for t in x])
78
+ df = df.rename(columns={'sub_tags_ids': 'tags'})
79
+
80
+ return df
81
+
82
+
83
+ def processing(df: pd.DataFrame) -> pd.DataFrame:
84
+ df = gluing(df)
85
+ df = add_reciever(df)
86
+ df = filter_data(df)
87
+ df = tagging(df)
88
+
89
+ return df
@@ -0,0 +1,8 @@
1
+ URLs = {
2
+ "schedule": "http://feeds.rustatsport.ru/?tpl=35&user={user}&key={password}&team_id={team_id}&season_id={season_id}&date_start=&date_end=&format=json",
3
+ "events": "http://feeds.rustatsport.ru/?tpl=36&user={user}&key={password}&match_id={match_id}&start_ms=0&dl=0&lang_id=1&format=json",
4
+ "match_stats": "http://feeds.rustatsport.ru/?tpl=207&user={user}&key={password}&match_id={match_id}&lang_id=1&format=json",
5
+ "tournament_teams": "http://feeds.rustatsport.ru/?tpl=32&user={user}&key={password}&tournament_id=2&season_id={season_id}&date_start=&date_end=&lang_id=1&format=json",
6
+ "tracking": "https://feeds.rustatsport.ru/?tpl=274&user={user}&key={password}&match_id={match_id}&lang_id=0&format=json",
7
+ "player_match_stats": "http://feeds.rustatsport.ru/?tpl=227&user={user}&key={password}&match_id={match_id}&lang_id=1&format=json"
8
+ }
@@ -0,0 +1 @@
1
+ Делайте че хотите с этим кодом.
@@ -0,0 +1,49 @@
1
+ Metadata-Version: 2.1
2
+ Name: rustat-python-api
3
+ Version: 0.4.11
4
+ Summary: A Python wrapper for RuStat API
5
+ Home-page: https://github.com/dailydaniel/rustat-python-api
6
+ Author: Daniel Zholkovsky
7
+ Author-email: daniel@zholkovsky.com
8
+ License: MIT
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: requests==2.32.3
16
+ Requires-Dist: pandas==2.2.3
17
+ Requires-Dist: tqdm==4.66.5
18
+
19
+ # rustat-python-api
20
+
21
+ ### Python wrapper for the Rustat API
22
+ ### Example of usage:
23
+ 0. Install the package:
24
+ ```bash
25
+ pip install rustat-python-api
26
+ ```
27
+ 1. Usage:
28
+ ```python
29
+ from rustat_python_api import RuStatParser
30
+
31
+ user = "your_login"
32
+ password = "your_password"
33
+
34
+ parser = RuStatParser(user, password)
35
+
36
+ info = parser.get_rpl_info()
37
+ keys = list(info.keys())
38
+ season_id, team_id = keys[-1], info[keys[-1]]["season_teams"][0]["id"]
39
+
40
+ schedule = parser.get_schedule(team_id, season_id)
41
+ keys = list(schedule.keys())
42
+ match_id = keys[-1]
43
+
44
+ events, subs = parser.get_events(match_id, process=True, return_subs=True)
45
+
46
+ stats = parser.get_match_stats(match_id)
47
+
48
+ tracking = parser.get_tracking(match_id)
49
+ ```
@@ -0,0 +1,11 @@
1
+ rustat_python_api/__init__.py,sha256=kjE2XCKbWzIIJ__xY2aoIj5aB6um197lgMlaaZ15iJ4,108
2
+ rustat_python_api/config.py,sha256=eMvi1p8Cfvnbp6Cd4bBOwgehVN7thKnaQV5uzWyGZXM,1844
3
+ rustat_python_api/models_api.py,sha256=oHXEqeCupvZwjVEdoxf7W9LP7ELFKA8-9DuRXpQHLno,1701
4
+ rustat_python_api/parser.py,sha256=kCKvpHYB8eNvrq64mEpGhWdUnJN8neMBFdwMbGAKWso,6913
5
+ rustat_python_api/processing.py,sha256=46O7wUGv5boWf4A38zYinbtqEC7ke-BwTdEg589XQaw,2816
6
+ rustat_python_api/urls.py,sha256=MIUDXq5QFacyVWbrIIuMbwf-WRARsxb32kFY97zw4OQ,865
7
+ rustat_python_api-0.4.11.dist-info/LICENSE,sha256=4Cohqg5p6Mq1xyrzdEX8AvFSA62GSVvapEOr2xK_tgY,57
8
+ rustat_python_api-0.4.11.dist-info/METADATA,sha256=4a4tT5JQ7YaMkocnPZnx3GQoGEo2Swya2FS3O0qdiR0,1248
9
+ rustat_python_api-0.4.11.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
10
+ rustat_python_api-0.4.11.dist-info/top_level.txt,sha256=VK0hmkKZE9YThxolUcoE6JtGI67NFeKJMBLuet8kI4w,18
11
+ rustat_python_api-0.4.11.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.44.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ rustat_python_api