Spaces:
Running
Running
Download scripts/build_var_dict.py from pablogrois/RRC: direct link, hf CLI and curl.
- Browser
- Download file 8.2 kB
-
https://huggingface.co/spaces/pablogrois/RRC/resolve/main/scripts/build_var_dict.py
- Command line
-
hf download hf://spaces/pablogrois/RRC/scripts/build_var_dict.py
-
curl -L -o build_var_dict.py https://huggingface.co/spaces/pablogrois/RRC/resolve/main/scripts/build_var_dict.py
8.2 kB
| """Genera el diccionario de variables (columna → nombre lindo + tipo) para la sección | |
| "Equipos y variables", a partir de ``diccionario_variables.xlsx``. | |
| El xlsx (3 columnas: Categoría · Nombre · Nombre Columna) NO está en el repo; vive | |
| en el disco del autor. Este script lo lee, matchea contra las columnas reales del | |
| agregado (case-insensitive) y escribe ``vendor/data/var_dict.json`` (chico, | |
| bundleado) que la app usa para mostrar nombres legibles y agrupar por tipo. | |
| El "tipo" top-level sale de la parte de la Categoría anterior al "–" | |
| (p.ej. "Defensiva – Presión" y "Defensiva" → "Defensiva"). | |
| Uso: python scripts/build_var_dict.py [ruta_xlsx] | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import sys | |
| from pathlib import Path | |
| sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) | |
| import pandas as pd | |
| from racing_reports import team_vars | |
| # Diccionario que manda el club. La copia buena vive en el repo; `sync_club_dictionary.py` | |
| # la usa para rellenar grupos/subgrupos y para podar var_labels_extra.json. | |
| XLSX = (Path(sys.argv[1]) if len(sys.argv) > 1 | |
| else Path(__file__).resolve().parents[1] / "diccionario_variables_2.xlsx") | |
| OUT = Path(__file__).resolve().parents[1] / "vendor" / "data" / "var_dict.json" | |
| # Grupos custom (Balón parado, Centros, Defensa, …) por feature — fuente editable. | |
| # Una feature puede estar en varios grupos. Reemplazan al 'tipo' en las secciones 2 y 3 de la app. | |
| GROUPS_FILE = Path(__file__).resolve().parent / "feature_groups.json" | |
| FEATURE_GROUPS = json.loads(GROUPS_FILE.read_text(encoding="utf-8")) if GROUPS_FILE.exists() else {} | |
| # Nombres de uso recuperados de "Estilos de juego - bloque.xlsx" para columnas que el | |
| # diccionario_variables.xlsx no nombra (si no, la app mostraba el nombre técnico crudo). | |
| EXTRA_FILE = Path(__file__).resolve().parent / "var_labels_extra.json" | |
| EXTRA_LABELS = json.loads(EXTRA_FILE.read_text(encoding="utf-8")) if EXTRA_FILE.exists() else {} | |
| # Subcategoría (``Categoria_2`` del diccionario del club): segundo nivel dentro del grupo. | |
| SUB_FILE = Path(__file__).resolve().parent / "feature_subgroups.json" | |
| FEATURE_SUBGROUPS = json.loads(SUB_FILE.read_text(encoding="utf-8")) if SUB_FILE.exists() else {} | |
| # ── lado de la variable: ¿mide algo NUESTRO o algo del rival / concedido? ──────────────── | |
| # Regla: termina en `_rival` o `_against` → es del rival. Cuatro excepciones revisadas a mano, | |
| # donde "rival" nombra la MITAD DE CANCHA del rival (la acción es nuestra) o el punto de vista | |
| # está invertido: | |
| LADO_PROPIA_AUNQUE_RIVAL = { | |
| "recuperaciones_campo_rival", # recuperaciones NUESTRAS en campo rival | |
| "altura_media_recuperacion_campo_rival", | |
| "desviacion_altura_media_recuperacion_campo_rival", | |
| "faltas_a_favor_rival", # = "Faltas Cometidas" (por nosotros) | |
| } | |
| def _lado(col: str) -> str: | |
| if col in LADO_PROPIA_AUNQUE_RIVAL: | |
| return "propia" | |
| return "rival" if (col.endswith("_rival") or col.endswith("_against")) else "propia" | |
| # Variables donde MENOS es mejor (rival / en contra / concedidas / errores propios). | |
| # Para estas, el "mejor de la liga" es el de valor (z) MÁS BAJO. Revisado una por una; | |
| # se EXCLUYEN las _rival neutrales/de estilo (altura offside, ancho, posición de pase, | |
| # dispersión, sweeper, % pases largos, recuperaciones del rival) y las buenas para | |
| # nosotros (tarjetas rojas del rival, faltas sufridas). | |
| NEGATIVE_COLS = { | |
| # --- rival: amenaza / concedido / progresión permitida --- | |
| "acciones_defensivas_en_ofensiva_rival", "big_chances_generadas_rival", | |
| "counter_press_recovery_rival", "duelos_aereos_ganados_rival", "faltas_a_favor_rival", | |
| "goles_corner_rival", "goles_regular_play_rival", "goles_tempranos_rival", | |
| "goles_totales_rival", "goles_transicion_rival", "pases_<20m_no_cross_rival", | |
| "pases_peligrosos_exitosos_con_centros_rival", "pases_progresivos_exitosos_rival", | |
| "pases_progresivos_rival", | |
| "pct_pases_exitosos_t1-2_rival", "pct_pases_exitosos_t3_rival", "pct_posesion_rival", | |
| "recepciones_area_rival", "ritmo_circulacion_pases_por_min_rival", "ritmo_progresion_rival", | |
| "shots_on_target_rival", "shots_rival", "situacion_fast_break_rival", | |
| "situacion_regular_play_rival", "throughballs_exitosos_rival", "throughballs_rival", | |
| "tiros_area_grande_rival", "tiros_area_pequeña_rival", "tiros_bloqueados_rival", | |
| "tiros_corner_rival", "tiros_fuera_de_area_rival", "touches_area_rival", | |
| "velocidad_progresion_m_por_s_rival", "xa_acumulado_rival", "xg_avg_tras_corner_rival", | |
| "xg_suma_tiros_rival", "xg_tiros_tras_corner_rival", "xgot_avg__tras_corner_rival", | |
| "xgot_suma_ontarget_rival", "xgot_tiros_tras_corner_rival", "xt_acumulado_rival", | |
| # --- rival: xG generado (todo xg_*_rival = malo) + recuperaciones / progresión permitida --- | |
| "recuperaciones_campo_rival_rival", "recuperaciones_campo_propio_rival", | |
| "avg_end_x_pass_rival", "pases_endx_1_2_tercio_rival", | |
| "xg_avg_vs_bloque_alto_rival", "xg_avg_vs_bloque_medio_rival", "xg_avg_vs_bloque_bajo_rival", | |
| "xg_vs_bloque_alto_rival", "xg_vs_bloque_medio_rival", "xg_vs_bloque_bajo_rival", | |
| "xg_secuencias_build_up_rival", | |
| "xg_tras_centro_inswinger_area_rival", "xg_tras_centro_inswinger_temprano_rival", | |
| "xg_tras_centro_outswinger_area_rival", "xg_tras_centro_outswinger_temprano_rival", | |
| "xg_tras_centro_raso_area_cutback_rival", "xg_tras_centro_raso_area_dividido_rival", | |
| "xg_tras_centro_raso_temprano_rival", "xg_tras_error_rival", "xg_tras_presion_alta_rival", | |
| "xg_tras_profundidad_corta_rival", "xg_tras_profundidad_larga_rival", | |
| "xg_tras_profundidad_media_rival", "xg_tras_recuperacion_tras_perdida_rival", | |
| "xg_tras_transicion_abp_rival", "xg_tras_transicion_bloque_bajo_medio_rival", | |
| "xg_tras_transicion_juego_directo_rival", | |
| # --- propias: no queremos muchos centros ni pelotazos del equipo propio --- | |
| "pct_centros_pases_totales_ultimo_tercio", "pct_pases_largos", | |
| # --- propias: errores / pérdidas / tarjetas / presión propia (ppda bajo = mejor) --- | |
| "errores_quederivan_tiro/gol", "perdidas_no_forzadas", "tarjetas_totales", | |
| "tarjetas_rojas", "gpi_perdidas", "ppda", | |
| } | |
| # Labels/tipos para columnas DERIVADAS que no están en el Excel del proveedor. | |
| MANUAL_LABELS = { | |
| # No está en el diccionario del club: la calculamos nosotros a partir de | |
| # cross_completados / cross_total (que sí están, pero como conteos crudos). | |
| "pct_exito_centros": {"label": "% Éxito Centros", "tipo": "Ofensiva"}, | |
| # El club escribe "Goles Temprano del rival"; mantenemos la concordancia. | |
| "goles_tempranos_rival": {"label": "Goles tempranos del rival", "tipo": "Defensiva"}, | |
| } | |
| def _top_type(cat: str) -> str: | |
| base = str(cat).split("–")[0].strip() # separador es en-dash U+2013 | |
| return "Estrategia" if base.upper() == "ESTRATEGIA" else base | |
| def main() -> None: | |
| dic = pd.read_excel(XLSX, sheet_name=0) | |
| by_col = {} | |
| for _, r in dic.iterrows(): | |
| col = str(r["Nombre Columna"]).strip().lower() | |
| by_col[col] = {"label": str(r["Nombre"]).strip(), "tipo": _top_type(r["Categoría"])} | |
| # mapear a las columnas reales del agregado (canónicas, en minúscula) | |
| out = {} | |
| for c in team_vars.variables(): | |
| info = (MANUAL_LABELS.get(c.lower()) or by_col.get(c.lower()) | |
| or ({"label": EXTRA_LABELS[c], "tipo": "Otras"} if c in EXTRA_LABELS else None) | |
| or {"label": c, "tipo": "Otras"}) | |
| info = dict(info) | |
| info["negativa"] = c in NEGATIVE_COLS | |
| info["lado"] = _lado(c) | |
| info["grupos"] = FEATURE_GROUPS.get(c, []) | |
| sub = FEATURE_SUBGROUPS.get(c) | |
| if sub: | |
| info["subgrupo"] = sub | |
| out[c] = info | |
| OUT.parent.mkdir(parents=True, exist_ok=True) | |
| OUT.write_text(json.dumps(out, ensure_ascii=False, indent=0), encoding="utf-8") | |
| n_lbl = sum(1 for v in out.values() if v["tipo"] != "Otras") | |
| tipos = sorted({v["tipo"] for v in out.values()}) | |
| print(f"Guardado {OUT} | {len(out)} columnas ({n_lbl} con nombre del diccionario) | tipos: {tipos}") | |
| if __name__ == "__main__": | |
| main() | |