UFCData 0.1.2__tar.gz → 0.2.0__tar.gz
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.
- {ufcdata-0.1.2 → ufcdata-0.2.0}/PKG-INFO +2 -1
- ufcdata-0.2.0/README.md +173 -0
- {ufcdata-0.1.2 → ufcdata-0.2.0}/UFCData.egg-info/PKG-INFO +2 -1
- {ufcdata-0.1.2 → ufcdata-0.2.0}/UFCData.egg-info/SOURCES.txt +2 -0
- {ufcdata-0.1.2 → ufcdata-0.2.0}/UFCData.egg-info/requires.txt +1 -0
- {ufcdata-0.1.2 → ufcdata-0.2.0}/pyproject.toml +2 -1
- ufcdata-0.2.0/ufcdata/__init__.py +3 -0
- {ufcdata-0.1.2 → ufcdata-0.2.0}/ufcdata/data.py +14 -0
- ufcdata-0.2.0/ufcdata/ratings.py +130 -0
- ufcdata-0.2.0/ufcdata/search.py +39 -0
- ufcdata-0.1.2/README.md +0 -0
- ufcdata-0.1.2/ufcdata/__init__.py +0 -1
- {ufcdata-0.1.2 → ufcdata-0.2.0}/LICENSE.md +0 -0
- {ufcdata-0.1.2 → ufcdata-0.2.0}/UFCData.egg-info/dependency_links.txt +0 -0
- {ufcdata-0.1.2 → ufcdata-0.2.0}/UFCData.egg-info/top_level.txt +0 -0
- {ufcdata-0.1.2 → ufcdata-0.2.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: UFCData
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Obtain UFC data and functions to manipulate it
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
License-File: LICENSE.md
|
|
@@ -8,4 +8,5 @@ Requires-Dist: huggingface-hub
|
|
|
8
8
|
Requires-Dist: pandas
|
|
9
9
|
Requires-Dist: numpy
|
|
10
10
|
Requires-Dist: glicko2
|
|
11
|
+
Requires-Dist: rapidfuzz
|
|
11
12
|
Dynamic: license-file
|
ufcdata-0.2.0/README.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# UFCData
|
|
2
|
+
|
|
3
|
+
An open-source Python package for accessing, manipulating, and analyzing UFC data.
|
|
4
|
+
|
|
5
|
+
https://pypi.org/project/UFCData/
|
|
6
|
+
|
|
7
|
+
add helper functions like convert odds
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Installation](#installation)
|
|
12
|
+
|
|
13
|
+
Accessing Data
|
|
14
|
+
- [Loading Data](#loading-data)
|
|
15
|
+
- [Search](#search)
|
|
16
|
+
|
|
17
|
+
Rating Functions
|
|
18
|
+
- [Elo](#elo)
|
|
19
|
+
- [Glicko-2](#glicko-2)
|
|
20
|
+
|
|
21
|
+
Fighter Functions
|
|
22
|
+
- [Fighter Record](#elo)
|
|
23
|
+
- [Fighter Statistic](#elo)
|
|
24
|
+
|
|
25
|
+
Model Evaluation
|
|
26
|
+
- [Odds Baseline Example](#elo)
|
|
27
|
+
|
|
28
|
+
Helper Functions
|
|
29
|
+
- [Odds Convert](#elo)
|
|
30
|
+
- [Weight Convert](#elo)
|
|
31
|
+
- [Gender Convert](#elo)
|
|
32
|
+
- [Mirror](#elo)
|
|
33
|
+
|
|
34
|
+
Data Sources
|
|
35
|
+
- [Online Sources](#online-sources)
|
|
36
|
+
- [Discrepancies in Data](#discrepancies-in-data)
|
|
37
|
+
- [Update Frequency](#update-frequency)
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install ufcdata
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Loading Data
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import ufcdata as ufc
|
|
49
|
+
data = ufc.get_data()
|
|
50
|
+
|
|
51
|
+
## Obtain fighter_bio dataframe
|
|
52
|
+
fighter_bio = data["fighter_bio"].copy()
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Note that data in here and other functions below reference data that is available prior to the data.
|
|
57
|
+
|
|
58
|
+
## Search
|
|
59
|
+
|
|
60
|
+
Due to various event naming conventions and fighters sharing the same name, the primary keys for the dataframes are links, which can be difficult for humans to interpret. The `search()` function uses fuzzy string matching to make it easier to find fighters, events, and other records.
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
search(data, column, query, matches=1)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Parameters
|
|
67
|
+
|
|
68
|
+
* `data` — The dataframe you are searching.
|
|
69
|
+
* `column` — The name of the column to search.
|
|
70
|
+
* `query` — The name or text you are searching for.
|
|
71
|
+
* `matches` — The number of closest matches to return. Defaults to `1`.
|
|
72
|
+
|
|
73
|
+
### Example
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
john_jones = ufc.search(fighter_bio, "Name", "Jon Jones", 3)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
This searches the `"Name"` column of the `fighter_bio` dataframe for the three closest matches to `"Jon Jones"`.
|
|
80
|
+
|
|
81
|
+
The results are returned as a dataframe, with the closest match appearing first.
|
|
82
|
+
|
|
83
|
+
Output:
|
|
84
|
+
|
|
85
|
+
```text
|
|
86
|
+
Name Nickname Height Weight Reach Stance Total W Total L Fighter Link
|
|
87
|
+
Jon Jones Bones 76.0 248.0 84.0 Orthodox 28 1 ufcstats.com/...
|
|
88
|
+
Roshaun Jones NaN 68.0 135.0 NaN NaN 2 6 ufcstats.com/...
|
|
89
|
+
Mason Jones The Dragon 70.0 155.0 74.0 Orthodox 18 2 ufcstats.com/...
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
This is useful when the exact value stored in the dataset is unknown or when there are multiple similar names. The same function can be used with any dataframe and column containing searchable text.
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
## Elo
|
|
96
|
+
|
|
97
|
+
UFCData provides an Elo rating system for calculating fighter ratings based on their previous fight results. Ratings are calculated chronologically, with each fighter's rating recorded **immediately before each fight**.
|
|
98
|
+
|
|
99
|
+
This allows Elo ratings to be used as features for predictive modeling without incorporating information from the fight being predicted.
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
ufc.get_elo(fight_df, fighter_df, r=1500, k=30, s=400)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Parameters
|
|
107
|
+
|
|
108
|
+
* `fight_df` — The UFC fight dataframe, sorted from most recent to least recent.
|
|
109
|
+
* `fighter_df` — The fighter information dataframe containing a `"Fighter Link"` column.
|
|
110
|
+
* `r` — Initial Elo rating for each fighter. Defaults to `1500`.
|
|
111
|
+
* `k` — K-factor controlling how much ratings change after each fight. Defaults to `30`.
|
|
112
|
+
* `s` — Scaling factor used when calculating expected scores. Defaults to `400`.
|
|
113
|
+
|
|
114
|
+
### Returns
|
|
115
|
+
|
|
116
|
+
The function returns a tuple containing:
|
|
117
|
+
|
|
118
|
+
1. `fighter_elo` — A dictionary mapping each fighter's UFCStats link to their final Elo rating.
|
|
119
|
+
2. `elo` — A dataframe containing fight-level Elo ratings. `"Fighter 1 Elo"` and `"Fighter 2 Elo"` represent each fighter's rating immediately before the corresponding fight.
|
|
120
|
+
|
|
121
|
+
### Example
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
import ufcdata as ufc
|
|
125
|
+
|
|
126
|
+
data = ufc.get_data()
|
|
127
|
+
|
|
128
|
+
fight_df = data["past_fights"].copy()
|
|
129
|
+
fighter_df = data["fighter_bio"].copy()
|
|
130
|
+
|
|
131
|
+
fighter_elo, fight_elo = ufc.get_elo(
|
|
132
|
+
fight_df,
|
|
133
|
+
fighter_df
|
|
134
|
+
)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The resulting `fight_elo` dataframe can then be used to examine or incorporate pre-fight Elo ratings into analysis and predictive models.
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
### Important
|
|
141
|
+
|
|
142
|
+
`fight_df` should be sorted from **most recent to least recent** before being passed to `get_elo()`. The function reverses the dataframe internally to process fights chronologically and returns the resulting data in the original order.
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
## Glicko-2
|
|
146
|
+
Glicko-2 extends the Elo system by incorporating rating deviation (RD) and volatility. Unlike Elo, which represents a fighter's ability with a single rating, Glicko-2 also estimates the uncertainty and consistency of that rating.
|
|
147
|
+
|
|
148
|
+
fighter_glicko, fight_glicko = ufc.glicko_2(
|
|
149
|
+
fight_df,
|
|
150
|
+
fighter_df
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
## Online Sources
|
|
154
|
+
|
|
155
|
+
Odds and birthplace data was obtained from https://www.tapology.com
|
|
156
|
+
|
|
157
|
+
Venue and attendance data was obtained from https://en.wikipedia.org/wiki/List_of_UFC_events
|
|
158
|
+
|
|
159
|
+
All other data was obtained from http://ufcstats.com
|
|
160
|
+
|
|
161
|
+
## Discrepancies in Data
|
|
162
|
+
|
|
163
|
+
UFCStats is treated as the authoritative source for UFCData. When discrepancies exist between UFCStats and other sources, such as Wikipedia or Tapology, the UFCStats data is used.
|
|
164
|
+
|
|
165
|
+
The UFCStats completed events page serves as the authoritative source for event, fight, and round data. Individual fighter profiles may contain fights from organizations or events that are not included in the completed events database, including WEC, Strikeforce, and PRIDE. These events are therefore excluded from UFCData.
|
|
166
|
+
|
|
167
|
+
## Update Frequency
|
|
168
|
+
|
|
169
|
+
The dataset is updated at the start of the scheduled broadcast time for each event. This update captures changes to betting odds, as well as any cancelled, postponed, or otherwise modified fights.
|
|
170
|
+
|
|
171
|
+
A second update occurs 24 hours after the start of the broadcast. This update captures the finalized event, fight, and round data, as well as information on upcoming events and fights.
|
|
172
|
+
|
|
173
|
+
Changes occurring between these scheduled updates are not automatically captured. Users are responsible for manually updating the dataset if they require the most current data for analysis or prediction.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: UFCData
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Obtain UFC data and functions to manipulate it
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
License-File: LICENSE.md
|
|
@@ -8,4 +8,5 @@ Requires-Dist: huggingface-hub
|
|
|
8
8
|
Requires-Dist: pandas
|
|
9
9
|
Requires-Dist: numpy
|
|
10
10
|
Requires-Dist: glicko2
|
|
11
|
+
Requires-Dist: rapidfuzz
|
|
11
12
|
Dynamic: license-file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "UFCData"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "Obtain UFC data and functions to manipulate it"
|
|
5
5
|
requires-python = ">=3.10"
|
|
6
6
|
|
|
@@ -9,4 +9,5 @@ dependencies = [
|
|
|
9
9
|
"pandas",
|
|
10
10
|
"numpy",
|
|
11
11
|
"glicko2",
|
|
12
|
+
"rapidfuzz",
|
|
12
13
|
]
|
|
@@ -3,6 +3,20 @@ import pickle
|
|
|
3
3
|
import logging
|
|
4
4
|
|
|
5
5
|
def get_data():
|
|
6
|
+
"""
|
|
7
|
+
Obtains all UFC related data
|
|
8
|
+
|
|
9
|
+
Returns
|
|
10
|
+
dict
|
|
11
|
+
Dictionary containing UFC data as pandas DataFrames.
|
|
12
|
+
Individual DataFrames can be accessed using their
|
|
13
|
+
corresponding dictionary keys.
|
|
14
|
+
|
|
15
|
+
Examples
|
|
16
|
+
--------
|
|
17
|
+
>>> data = get_data()
|
|
18
|
+
>>> past_events = data["past_events"].copy()
|
|
19
|
+
"""
|
|
6
20
|
|
|
7
21
|
logging.getLogger("huggingface_hub").setLevel(logging.ERROR)
|
|
8
22
|
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
from glicko2 import Player
|
|
3
|
+
|
|
4
|
+
#@title Elo function
|
|
5
|
+
def expected_score(rating, opponent_rating, s):
|
|
6
|
+
return (1)/(1 + 10**((opponent_rating - rating)/(s)))
|
|
7
|
+
|
|
8
|
+
#Rating update
|
|
9
|
+
def update_rating(rating, k, outcome, expected):
|
|
10
|
+
return rating + k*(outcome - expected)
|
|
11
|
+
|
|
12
|
+
# Elo function
|
|
13
|
+
# Make sure fights are sorted from most recent to least recent. Gets Elo prior to fight.
|
|
14
|
+
def get_elo(fight_df, fighter_df, r=1500, k=30, s=400):
|
|
15
|
+
"""
|
|
16
|
+
Calculates pre-fight Elo ratings for UFC fighters.
|
|
17
|
+
|
|
18
|
+
Fights are processed chronologically from oldest to newest, with
|
|
19
|
+
each fighter's Elo rating recorded immediately before each fight.
|
|
20
|
+
Fighter ratings are initialized to `r` and updated after each fight
|
|
21
|
+
using the specified K-factor and Elo scale.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
fight_df : pandas.DataFrame
|
|
26
|
+
UFC fight data sorted from most recent to least recent.
|
|
27
|
+
Must contain fighter links, fight outcomes, and the fight
|
|
28
|
+
information required to construct the output.
|
|
29
|
+
fighter_df : pandas.DataFrame
|
|
30
|
+
DataFrame containing fighter information. Must contain a
|
|
31
|
+
"Fighter Link" column used to identify each fighter.
|
|
32
|
+
r : float, default=1500
|
|
33
|
+
Initial Elo rating assigned to each fighter.
|
|
34
|
+
k : float, default=30
|
|
35
|
+
K-factor controlling the magnitude of Elo rating changes
|
|
36
|
+
after each fight.
|
|
37
|
+
s : float, default=400
|
|
38
|
+
Elo scaling factor used when calculating expected scores.
|
|
39
|
+
|
|
40
|
+
Returns
|
|
41
|
+
-------
|
|
42
|
+
current_elo : dict
|
|
43
|
+
Dictionary mapping each fighter's link to their current Elo rating
|
|
44
|
+
after processing all fights.
|
|
45
|
+
|
|
46
|
+
past_elo : pandas.DataFrame
|
|
47
|
+
Fight-level DataFrame containing the original fight information
|
|
48
|
+
and the Elo rating of each fighter immediately before the fight.
|
|
49
|
+
|
|
50
|
+
Notes
|
|
51
|
+
-----
|
|
52
|
+
The input fight data should be sorted from most recent to least
|
|
53
|
+
recent. The function reverses this order internally to process
|
|
54
|
+
fights chronologically, then returns the resulting data in the
|
|
55
|
+
original order.
|
|
56
|
+
|
|
57
|
+
Examples
|
|
58
|
+
--------
|
|
59
|
+
>>> fighter_elo, fight_elo = get_elo(fight_df, fighter_df)
|
|
60
|
+
>>> fight_elo[["Fighter 1", "Fighter 1 Elo",
|
|
61
|
+
... "Fighter 2", "Fighter 2 Elo"]].head()
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
fight_df = fight_df[::-1]
|
|
65
|
+
|
|
66
|
+
original_fight_df = fight_df.copy()
|
|
67
|
+
|
|
68
|
+
fighter_1_elo = []
|
|
69
|
+
fighter_2_elo = []
|
|
70
|
+
|
|
71
|
+
## Create a dictionary of fighters and their Elo
|
|
72
|
+
fighter_df = fighter_df.copy()
|
|
73
|
+
fighter_df["Elo"] = r
|
|
74
|
+
fighter_df = dict(zip(fighter_df["Fighter Link"], fighter_df["Elo"]))
|
|
75
|
+
|
|
76
|
+
## Create a list of fights
|
|
77
|
+
fight_df = fight_df[["Fighter 1 Link", "Fighter 1 Outcome", "Fighter 2 Link", "Fighter 2 Outcome"]]
|
|
78
|
+
fight_df = fight_df.values.tolist()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
## Loop through each fight in order
|
|
82
|
+
for fight in fight_df:
|
|
83
|
+
## Get fighters Elo from fighter_df
|
|
84
|
+
elo_1 = fighter_df[fight[0]]
|
|
85
|
+
elo_2 = fighter_df[fight[2]]
|
|
86
|
+
## Add to final list
|
|
87
|
+
fighter_1_elo.append(elo_1)
|
|
88
|
+
fighter_2_elo.append(elo_2)
|
|
89
|
+
## Update Elo
|
|
90
|
+
if (fight[1] == "W") and ((fight[3] == "L")):
|
|
91
|
+
expected_1 = expected_score(elo_1, elo_2, s)
|
|
92
|
+
elo_1_new = update_rating(elo_1, k, 1, expected_1)
|
|
93
|
+
expected_2 = expected_score(elo_2, elo_1, s)
|
|
94
|
+
elo_2_new = update_rating(elo_2, k, 0, expected_2)
|
|
95
|
+
fighter_df[fight[0]] = elo_1_new
|
|
96
|
+
fighter_df[fight[2]] = elo_2_new
|
|
97
|
+
elif (fight[1] == "L") and (fight[3] == "W"):
|
|
98
|
+
expected_1 = expected_score(elo_1, elo_2, s)
|
|
99
|
+
elo_1_new = update_rating(elo_1, k, 0, expected_1)
|
|
100
|
+
expected_2 = expected_score(elo_2, elo_1, s)
|
|
101
|
+
elo_2_new = update_rating(elo_2, k, 1, expected_2)
|
|
102
|
+
fighter_df[fight[0]] = elo_1_new
|
|
103
|
+
fighter_df[fight[2]] = elo_2_new
|
|
104
|
+
elif (fight[1] == "D") and (fight[3] == "D"):
|
|
105
|
+
expected_1 = expected_score(elo_1, elo_2, s)
|
|
106
|
+
elo_1_new = update_rating(elo_1, k, 0.5, expected_1)
|
|
107
|
+
expected_2 = expected_score(elo_2, elo_1, s)
|
|
108
|
+
elo_2_new = update_rating(elo_2, k, 0.5, expected_2)
|
|
109
|
+
fighter_df[fight[0]] = elo_1_new
|
|
110
|
+
fighter_df[fight[2]] = elo_2_new
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
elo_1 = pd.DataFrame(fighter_1_elo, columns=["Fighter 1 Elo"])
|
|
114
|
+
elo_2 = pd.DataFrame(fighter_2_elo, columns=["Fighter 2 Elo"])
|
|
115
|
+
elo = pd.concat(
|
|
116
|
+
[
|
|
117
|
+
original_fight_df.reset_index(drop=True),
|
|
118
|
+
elo_1.reset_index(drop=True),
|
|
119
|
+
elo_2.reset_index(drop=True)
|
|
120
|
+
],
|
|
121
|
+
axis=1
|
|
122
|
+
)
|
|
123
|
+
elo = elo[['Date', 'Event Link', 'Fight Number', 'Fight Link', 'Weight Class',
|
|
124
|
+
'Gender', 'Title', 'Fighter 1', 'Fighter 1 Elo', 'Fighter 1 Odds', 'Fighter 1 Link',
|
|
125
|
+
'Fighter 1 Outcome', 'Fighter 1 Bonus', 'Fighter 2','Fighter 2 Elo', 'Fighter 2 Odds',
|
|
126
|
+
'Fighter 2 Link', 'Fighter 2 Outcome', 'Fighter 2 Bonus', 'Method',
|
|
127
|
+
'Round', 'Time', 'Time Format', 'Referee', 'Details']]
|
|
128
|
+
past_elo = elo[::-1].copy()
|
|
129
|
+
current_elo = fighter_df
|
|
130
|
+
return (current_elo, past_elo)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from rapidfuzz.fuzz import ratio
|
|
2
|
+
|
|
3
|
+
def search(data, column , query, matches=1):
|
|
4
|
+
"""
|
|
5
|
+
Searches a UFC DataFrame for rows matching a query.
|
|
6
|
+
|
|
7
|
+
Uses fuzzy string matching to identify the rows in the specified
|
|
8
|
+
column that most closely match the query.
|
|
9
|
+
|
|
10
|
+
Parameters
|
|
11
|
+
----------
|
|
12
|
+
data : pandas.DataFrame
|
|
13
|
+
UFC DataFrame to search.
|
|
14
|
+
column : str
|
|
15
|
+
Name of the column to search.
|
|
16
|
+
query : str
|
|
17
|
+
Search query to match against the specified column.
|
|
18
|
+
matches : int, default=1
|
|
19
|
+
Number of closest matches to return.
|
|
20
|
+
|
|
21
|
+
Returns
|
|
22
|
+
-------
|
|
23
|
+
pandas.DataFrame
|
|
24
|
+
DataFrame containing the closest matching rows, sorted from
|
|
25
|
+
highest to lowest similarity.
|
|
26
|
+
|
|
27
|
+
Examples
|
|
28
|
+
--------
|
|
29
|
+
>>> data = get_data()
|
|
30
|
+
>>> results = search(data["fighters"], "Name", "Jon Jones")
|
|
31
|
+
"""
|
|
32
|
+
scores = data[column].fillna("").apply(
|
|
33
|
+
lambda x: ratio(str(x), query))
|
|
34
|
+
|
|
35
|
+
result = data.loc[scores.nlargest(matches).index].copy()
|
|
36
|
+
result["score"] = scores.loc[result.index]
|
|
37
|
+
result = result.sort_values("score", ascending=False).drop(columns="score")
|
|
38
|
+
|
|
39
|
+
return result
|
ufcdata-0.1.2/README.md
DELETED
|
File without changes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from .data import get_data
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|