Spaces:
Sleeping
Sleeping
Upload 2 files
Browse files- app.py +40 -42
- requirements.txt +1 -1
app.py
CHANGED
|
@@ -1,16 +1,8 @@
|
|
| 1 |
# app.py
|
| 2 |
-
#
|
| 3 |
-
#
|
| 4 |
-
# Coloca estos archivos en el repo del Space (misma carpeta que app.py):
|
| 5 |
-
# - rf_win_model.pkl
|
| 6 |
-
# - input_schema.json
|
| 7 |
-
# - model_metadata.json
|
| 8 |
-
# - faker.gif
|
| 9 |
-
# - requirements.txt
|
| 10 |
-
#
|
| 11 |
-
# Nota: para que el GIF se anime, se usa gr.HTML con <img src="file=faker.gif">.
|
| 12 |
|
| 13 |
import json
|
|
|
|
| 14 |
import joblib
|
| 15 |
import pandas as pd
|
| 16 |
import gradio as gr
|
|
@@ -18,10 +10,28 @@ import gradio as gr
|
|
| 18 |
MODEL_PKL = "rf_win_model.pkl"
|
| 19 |
SCHEMA_JSON = "input_schema.json"
|
| 20 |
META_JSON = "model_metadata.json"
|
|
|
|
| 21 |
|
| 22 |
-
# Si True: recorta inputs al rango observado min-max antes de predecir
|
| 23 |
CLIP_TO_OBSERVED = True
|
| 24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
# -------------------------
|
| 26 |
# Load artifacts
|
| 27 |
# -------------------------
|
|
@@ -36,7 +46,6 @@ with open(META_JSON, "r", encoding="utf-8") as f:
|
|
| 36 |
FEATURES = meta["features_order"]
|
| 37 |
feat_map = {f["name"]: f for f in schema["features"]}
|
| 38 |
|
| 39 |
-
# Etiquetas humanas
|
| 40 |
UI_LABELS = {
|
| 41 |
"TeamTotalGold": "Oro total del equipo",
|
| 42 |
"TeamXp": "Experiencia total del equipo",
|
|
@@ -48,16 +57,12 @@ UI_LABELS = {
|
|
| 48 |
"TeamControlWardsPlaced": "Wards de control colocados",
|
| 49 |
}
|
| 50 |
|
| 51 |
-
# Agrupar inputs para UI
|
| 52 |
GROUPS = {
|
| 53 |
"Economía y pelea": ["TeamTotalGold", "TeamXp", "TeamTotalKills"],
|
| 54 |
"Objetivos": ["TeamDragonKills", "TeamHeraldKills", "TeamTurretPlatesDestroyed"],
|
| 55 |
"Visión": ["TeamWardsPlaced", "TeamControlWardsPlaced"],
|
| 56 |
}
|
| 57 |
|
| 58 |
-
def fmt_int(x: float) -> str:
|
| 59 |
-
return f"{int(round(x))}"
|
| 60 |
-
|
| 61 |
def reset_values():
|
| 62 |
return [float(feat_map[c]["ui_default"]) for c in FEATURES]
|
| 63 |
|
|
@@ -117,13 +122,12 @@ def predict(*vals):
|
|
| 117 |
return proba, verdict, warn_text
|
| 118 |
|
| 119 |
# -------------------------
|
| 120 |
-
# Build inputs (
|
| 121 |
# -------------------------
|
| 122 |
input_components_by_col = {}
|
| 123 |
|
| 124 |
for col in FEATURES:
|
| 125 |
info = feat_map[col]
|
| 126 |
-
|
| 127 |
rec_min = float(info["p05"])
|
| 128 |
rec_max = float(info["p95"])
|
| 129 |
obs_min = float(info["min"])
|
|
@@ -176,10 +180,20 @@ CSS = """
|
|
| 176 |
|
| 177 |
theme = gr.themes.Soft()
|
| 178 |
|
| 179 |
-
|
| 180 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
|
| 182 |
-
|
|
|
|
| 183 |
with gr.Row():
|
| 184 |
with gr.Column(scale=3):
|
| 185 |
gr.Markdown(
|
|
@@ -189,26 +203,12 @@ Formulario para estimar probabilidad de ganar a partir de métricas del equipo e
|
|
| 189 |
""".strip()
|
| 190 |
)
|
| 191 |
gr.Markdown(
|
| 192 |
-
"<div class='small'>Ingresa valores y presiona
|
| 193 |
)
|
| 194 |
-
|
| 195 |
with gr.Column(scale=2):
|
| 196 |
-
gr.HTML(
|
| 197 |
-
"""
|
| 198 |
-
<div class="gifbox">
|
| 199 |
-
<img src="file=faker.gif" alt="Faker GIF">
|
| 200 |
-
<div class="muted" style="margin-top:8px;">
|
| 201 |
-
Fuente del GIF:
|
| 202 |
-
<a href="https://thegamehaus.com/league-of-legends/league-of-legends-faker-history-of-success/2019/04/14/" target="_blank" rel="noopener noreferrer">
|
| 203 |
-
TheGameHaus (2019)
|
| 204 |
-
</a>
|
| 205 |
-
</div>
|
| 206 |
-
</div>
|
| 207 |
-
""".strip()
|
| 208 |
-
)
|
| 209 |
|
| 210 |
with gr.Row():
|
| 211 |
-
# Inputs
|
| 212 |
with gr.Column(scale=3):
|
| 213 |
with gr.Column(elem_classes="card"):
|
| 214 |
gr.Markdown("## Ingresa los valores de cada variable")
|
|
@@ -222,7 +222,6 @@ Formulario para estimar probabilidad de ganar a partir de métricas del equipo e
|
|
| 222 |
btn_predict = gr.Button("Predecir", variant="primary")
|
| 223 |
btn_reset = gr.Button("Restaurar valores típicos", variant="secondary")
|
| 224 |
|
| 225 |
-
# Outputs
|
| 226 |
with gr.Column(scale=2):
|
| 227 |
with gr.Column(elem_classes="card"):
|
| 228 |
gr.Markdown("## Resultado")
|
|
@@ -236,9 +235,8 @@ Formulario para estimar probabilidad de ganar a partir de métricas del equipo e
|
|
| 236 |
"""
|
| 237 |
<div class="small">
|
| 238 |
El modelo fue entrenado con el dataset de Kaggle:
|
| 239 |
-
<a href="https://www.kaggle.com/datasets/karlorusovan/league-of-legends-soloq-matches-at-10-minutes-2024/data"
|
| 240 |
-
League of Legends SoloQ Matches at 10 Minutes (2024)
|
| 241 |
-
</a>.
|
| 242 |
</div>
|
| 243 |
""".strip()
|
| 244 |
)
|
|
@@ -255,4 +253,4 @@ League of Legends SoloQ Matches at 10 Minutes (2024)
|
|
| 255 |
outputs=inputs_in_order,
|
| 256 |
)
|
| 257 |
|
| 258 |
-
demo.launch()
|
|
|
|
| 1 |
# app.py
|
| 2 |
+
# Gradio Space: formulario numérico + GIF embebido (base64) + créditos
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
|
| 4 |
import json
|
| 5 |
+
import base64
|
| 6 |
import joblib
|
| 7 |
import pandas as pd
|
| 8 |
import gradio as gr
|
|
|
|
| 10 |
MODEL_PKL = "rf_win_model.pkl"
|
| 11 |
SCHEMA_JSON = "input_schema.json"
|
| 12 |
META_JSON = "model_metadata.json"
|
| 13 |
+
GIF_PATH = "faker.gif"
|
| 14 |
|
|
|
|
| 15 |
CLIP_TO_OBSERVED = True
|
| 16 |
|
| 17 |
+
# -------------------------
|
| 18 |
+
# Helpers
|
| 19 |
+
# -------------------------
|
| 20 |
+
def gif_data_uri(path: str) -> str:
|
| 21 |
+
"""
|
| 22 |
+
Devuelve un data URI base64 para un GIF local.
|
| 23 |
+
Si el archivo no existe, devuelve string vacío.
|
| 24 |
+
"""
|
| 25 |
+
try:
|
| 26 |
+
with open(path, "rb") as f:
|
| 27 |
+
b64 = base64.b64encode(f.read()).decode("utf-8")
|
| 28 |
+
return f"data:image/gif;base64,{b64}"
|
| 29 |
+
except FileNotFoundError:
|
| 30 |
+
return ""
|
| 31 |
+
|
| 32 |
+
def fmt_int(x: float) -> str:
|
| 33 |
+
return f"{int(round(x))}"
|
| 34 |
+
|
| 35 |
# -------------------------
|
| 36 |
# Load artifacts
|
| 37 |
# -------------------------
|
|
|
|
| 46 |
FEATURES = meta["features_order"]
|
| 47 |
feat_map = {f["name"]: f for f in schema["features"]}
|
| 48 |
|
|
|
|
| 49 |
UI_LABELS = {
|
| 50 |
"TeamTotalGold": "Oro total del equipo",
|
| 51 |
"TeamXp": "Experiencia total del equipo",
|
|
|
|
| 57 |
"TeamControlWardsPlaced": "Wards de control colocados",
|
| 58 |
}
|
| 59 |
|
|
|
|
| 60 |
GROUPS = {
|
| 61 |
"Economía y pelea": ["TeamTotalGold", "TeamXp", "TeamTotalKills"],
|
| 62 |
"Objetivos": ["TeamDragonKills", "TeamHeraldKills", "TeamTurretPlatesDestroyed"],
|
| 63 |
"Visión": ["TeamWardsPlaced", "TeamControlWardsPlaced"],
|
| 64 |
}
|
| 65 |
|
|
|
|
|
|
|
|
|
|
| 66 |
def reset_values():
|
| 67 |
return [float(feat_map[c]["ui_default"]) for c in FEATURES]
|
| 68 |
|
|
|
|
| 122 |
return proba, verdict, warn_text
|
| 123 |
|
| 124 |
# -------------------------
|
| 125 |
+
# Build inputs (Number) con rangos en label (sin min/max)
|
| 126 |
# -------------------------
|
| 127 |
input_components_by_col = {}
|
| 128 |
|
| 129 |
for col in FEATURES:
|
| 130 |
info = feat_map[col]
|
|
|
|
| 131 |
rec_min = float(info["p05"])
|
| 132 |
rec_max = float(info["p95"])
|
| 133 |
obs_min = float(info["min"])
|
|
|
|
| 180 |
|
| 181 |
theme = gr.themes.Soft()
|
| 182 |
|
| 183 |
+
gif_uri = gif_data_uri(GIF_PATH)
|
| 184 |
+
gif_html = f"""
|
| 185 |
+
<div class="gifbox">
|
| 186 |
+
{"<img src='" + gif_uri + "' alt='Faker GIF'>" if gif_uri else "<div class='muted'>No se encontró faker.gif en el repo.</div>"}
|
| 187 |
+
<div class="muted" style="margin-top:8px;">
|
| 188 |
+
Fuente del GIF:
|
| 189 |
+
<a href="https://thegamehaus.com/league-of-legends/league-of-legends-faker-history-of-success/2019/04/14/"
|
| 190 |
+
target="_blank" rel="noopener noreferrer">TheGameHaus (2019)</a>
|
| 191 |
+
</div>
|
| 192 |
+
</div>
|
| 193 |
+
""".strip()
|
| 194 |
|
| 195 |
+
with gr.Blocks(title="Predicción de victoria (demo)") as demo:
|
| 196 |
+
with gr.Column(elem_id="wrap"):
|
| 197 |
with gr.Row():
|
| 198 |
with gr.Column(scale=3):
|
| 199 |
gr.Markdown(
|
|
|
|
| 203 |
""".strip()
|
| 204 |
)
|
| 205 |
gr.Markdown(
|
| 206 |
+
"<div class='small'>Ingresa valores y presiona Predecir. Si te sales de rangos típicos, la app te avisa (y puede ajustar al rango observado).</div>"
|
| 207 |
)
|
|
|
|
| 208 |
with gr.Column(scale=2):
|
| 209 |
+
gr.HTML(gif_html)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
|
| 211 |
with gr.Row():
|
|
|
|
| 212 |
with gr.Column(scale=3):
|
| 213 |
with gr.Column(elem_classes="card"):
|
| 214 |
gr.Markdown("## Ingresa los valores de cada variable")
|
|
|
|
| 222 |
btn_predict = gr.Button("Predecir", variant="primary")
|
| 223 |
btn_reset = gr.Button("Restaurar valores típicos", variant="secondary")
|
| 224 |
|
|
|
|
| 225 |
with gr.Column(scale=2):
|
| 226 |
with gr.Column(elem_classes="card"):
|
| 227 |
gr.Markdown("## Resultado")
|
|
|
|
| 235 |
"""
|
| 236 |
<div class="small">
|
| 237 |
El modelo fue entrenado con el dataset de Kaggle:
|
| 238 |
+
<a href="https://www.kaggle.com/datasets/karlorusovan/league-of-legends-soloq-matches-at-10-minutes-2024/data"
|
| 239 |
+
target="_blank" rel="noopener noreferrer">League of Legends SoloQ Matches at 10 Minutes (2024)</a>.
|
|
|
|
| 240 |
</div>
|
| 241 |
""".strip()
|
| 242 |
)
|
|
|
|
| 253 |
outputs=inputs_in_order,
|
| 254 |
)
|
| 255 |
|
| 256 |
+
demo.launch(css=CSS, theme=theme, ssr_mode=False)
|
requirements.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
gradio
|
| 2 |
pandas
|
| 3 |
numpy
|
| 4 |
-
scikit-learn
|
| 5 |
joblib
|
| 6 |
openpyxl
|
|
|
|
| 1 |
gradio
|
| 2 |
pandas
|
| 3 |
numpy
|
| 4 |
+
scikit-learn==1.4.2
|
| 5 |
joblib
|
| 6 |
openpyxl
|