Download app.py from Zipeng365/TSDecompose-Benchmark-Leaderboard: direct link, hf CLI and curl.
- Browser
- Download file 24.9 kB
-
https://huggingface.co/spaces/Zipeng365/TSDecompose-Benchmark-Leaderboard/resolve/main/app.py
- Command line
-
hf download hf://spaces/Zipeng365/TSDecompose-Benchmark-Leaderboard/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Zipeng365/TSDecompose-Benchmark-Leaderboard/resolve/main/app.py
24.9 kB
| import json | |
| import os | |
| from functools import lru_cache | |
| from html import escape | |
| from pathlib import Path | |
| import gradio as gr | |
| import pandas as pd | |
| import plotly.graph_objects as go | |
| from huggingface_hub import hf_hub_download | |
| DATASET_REPO = os.getenv("TSDECOMPOSE_DATASET_REPO", "Zipeng365/TSDecompose-Benchmark") | |
| DATA_VERSION = os.getenv("TSDECOMPOSE_DATA_VERSION", "v1.0.0") | |
| LOCAL_DATA_DIR = os.getenv("TSDECOMPOSE_LOCAL_DATA_DIR") | |
| SITE_FILES = { | |
| "paper_global": "data/paper_tables/global_performance_summary.csv", | |
| "paper_radar": "data/paper_figures/selected_radar_charts.png", | |
| "synthetic": f"site_data/{DATA_VERSION}/leaderboard_synthetic_full22_overall.csv", | |
| "synthetic_rank": "data/synthetic_full22_extension/results/summary/ranking_paper_5metric_overall.csv", | |
| "real_proxy": f"site_data/{DATA_VERSION}/leaderboard_real_proxy22_overall.csv", | |
| "semisynth": f"site_data/{DATA_VERSION}/leaderboard_semisynth_transfer_by_method.csv", | |
| "semisynth_rank": "data/semisynth_transfer/results/summary/ranking_paper_5metric_overall.csv", | |
| "track_b": f"site_data/{DATA_VERSION}/leaderboard_post_rebuttal_real_physics_track_b.csv", | |
| "track_c": f"site_data/{DATA_VERSION}/leaderboard_post_rebuttal_real_proxy_track_c.csv", | |
| "methods": f"site_data/{DATA_VERSION}/methods.json", | |
| "suites": f"site_data/{DATA_VERSION}/suites.json", | |
| "metrics": f"site_data/{DATA_VERSION}/evaluation_metrics.json", | |
| } | |
| VIEW_LABELS = { | |
| "paper_global": "Paper Table 2 / Figure 3 Anchors", | |
| "synthetic": "Benchmark 22-Method Synthetic Extension", | |
| "real_proxy": "Benchmark 22-Method Real Proxy", | |
| "semisynth": "Benchmark 22-Method Semi-Synthetic Transfer", | |
| "track_b": "Post-Rebuttal Track B: CO2 / Tides", | |
| "track_c": "Post-Rebuttal Track C: Real Proxy Panel", | |
| } | |
| VIEW_NOTES = { | |
| "paper_global": ( | |
| "Camera-ready paper-aligned view. This reproduces Table 2's six-family " | |
| "global performance summary from the 6-scenario, 50-draw core protocol. " | |
| "Table 2 reports two of the five paper-core component-recovery metrics, " | |
| "using tier-balanced stationary aggregation over Tiers 1-2 and Tier 3 for " | |
| "the non-stationary columns; Figure 3 shows the " | |
| "five-axis capability profile. This is not a single universal ranking." | |
| ), | |
| "synthetic": ( | |
| "Benchmark-only expanded synthetic board: 6 scenarios x 50 draws = 300 generated " | |
| "time-series instances. The expanded roster gives 22 method rows, " | |
| "or 6,600 method-instance evaluations. This is a living extension view, not the " | |
| "paper's primary Table 2/Figure 3 view. It reuses the five component-recovery " | |
| "metrics and uses the released five-metric mean within-setting ranking." | |
| ), | |
| "real_proxy": ( | |
| "Benchmark-only real-data companion track. These are proxy diagnostics, " | |
| "not exact component-recovery labels and not camera-ready paper rankings. " | |
| "Lower overall mean rank is better." | |
| ), | |
| "semisynth": ( | |
| "Benchmark-only 22-method semi-synthetic transfer track: known synthetic " | |
| "mechanisms injected into six real monthly backgrounds with three mechanisms, " | |
| "two background scales, and eight windows per setting. This is a living " | |
| "benchmark extension, not a camera-ready paper table; the displayed rank uses " | |
| "the released five-metric mean within-setting ranking." | |
| ), | |
| "track_b": ( | |
| "Post-rebuttal mechanism-aware checks on CO2 and tides. These use " | |
| "mechanism-informed approximate structure and should be read as companion evidence." | |
| ), | |
| "track_c": ( | |
| "Post-rebuttal six-dataset proxy/stability panel. These rows measure " | |
| "plausibility and robustness where exact real-world components are unavailable." | |
| ), | |
| } | |
| HIGHER_IS_BETTER = { | |
| "stationary_trend_r2": True, | |
| "stationary_seasonal_spectral_corr": True, | |
| "nonstationary_trend_r2": True, | |
| "nonstationary_seasonal_spectral_corr": True, | |
| "metric_T_r2_mean": True, | |
| "metric_S_r2_mean": True, | |
| "metric_S_spectral_corr_mean": True, | |
| "metric_S_maxlag_corr_mean": True, | |
| "coverage_success": True, | |
| "metric_T_r2": True, | |
| "metric_S_r2": True, | |
| "metric_S_spectral_corr": True, | |
| "metric_S_maxlag_corr": True, | |
| "trend_r2": True, | |
| "seasonal_r2": True, | |
| "quad_fit_r2": True, | |
| "seasonal_amplitude_slope_sign_match": True, | |
| "target_frequency_coverage": True, | |
| "m2_s2_separation_success": True, | |
| "band_plausibility": True, | |
| "band_power_ratio": True, | |
| "band_peak_hit": True, | |
| "resampling_trend_corr": True, | |
| "resampling_season_corr": True, | |
| "resampling_season_spectral_corr": True, | |
| "resampling_season_maxlag_corr": True, | |
| "resampling_band_overlap": True, | |
| "resampling_stability": True, | |
| "temporal_spectrum_overlap": True, | |
| "overall_mean_rank": False, | |
| "metric_T_dtw_mean": False, | |
| "metric_T_dtw": False, | |
| "c2_relative_error": False, | |
| "seasonal_amplitude_slope_error": False, | |
| "main_peak_frequency_error": False, | |
| "temporal_trend_smoothness_drift": False, | |
| "temporal_dominant_frequency_drift": False, | |
| "mean_metric_rank": False, | |
| } | |
| DEFAULT_METRIC = { | |
| "paper_global": "stationary_trend_r2", | |
| "synthetic": "overall_mean_rank", | |
| "real_proxy": "overall_mean_rank", | |
| "semisynth": "overall_mean_rank", | |
| "track_b": "seasonal_r2", | |
| "track_c": "band_plausibility", | |
| } | |
| COLUMN_LABELS = { | |
| "stationary_trend_r2": "Stationary Trend R2", | |
| "stationary_seasonal_spectral_corr": "Stationary Seasonal Spectral Corr", | |
| "nonstationary_trend_r2": "Non-stationary Trend R2", | |
| "nonstationary_seasonal_spectral_corr": "Non-stationary Seasonal Spectral Corr", | |
| "metric_T_r2": "Trend R2", | |
| "metric_T_r2_mean": "Trend R2 mean", | |
| "metric_T_dtw": "Trend DTW", | |
| "metric_T_dtw_mean": "Trend DTW mean", | |
| "metric_S_r2": "Seasonal R2", | |
| "metric_S_r2_mean": "Seasonal R2 mean", | |
| "metric_S_spectral_corr": "Seasonal spectral corr", | |
| "metric_S_spectral_corr_mean": "Seasonal spectral corr mean", | |
| "metric_S_maxlag_corr": "Seasonal max-lag corr", | |
| "metric_S_maxlag_corr_mean": "Seasonal max-lag corr mean", | |
| "mean_metric_rank": "Mean metric rank", | |
| "overall_mean_rank": "Overall mean rank", | |
| "overall_std_rank": "Overall rank SD", | |
| "coverage_success": "Coverage", | |
| "row_count": "Rows", | |
| "setting_count": "Settings", | |
| } | |
| TABLE_LABELS = { | |
| "display_name": "Method", | |
| "family": "Family", | |
| "method": "Method ID", | |
| "stationary_trend_r2": "Stat. T R2", | |
| "stationary_seasonal_spectral_corr": "Stat. S spec.", | |
| "nonstationary_trend_r2": "Nonstat. T R2", | |
| "nonstationary_seasonal_spectral_corr": "Nonstat. S spec.", | |
| "metric_T_r2_mean": "T R2", | |
| "metric_T_dtw_mean": "T DTW", | |
| "metric_S_r2_mean": "S R2", | |
| "metric_S_spectral_corr_mean": "S spec.", | |
| "metric_S_maxlag_corr_mean": "S max-lag", | |
| "overall_mean_rank": "Mean rank", | |
| "overall_std_rank": "Rank SD", | |
| } | |
| PAPER_GLOBAL_METRICS = [ | |
| "stationary_trend_r2", | |
| "stationary_seasonal_spectral_corr", | |
| "nonstationary_trend_r2", | |
| "nonstationary_seasonal_spectral_corr", | |
| ] | |
| FAMILY_COLORS = { | |
| "paper family": "#2563eb", | |
| "classical": "#2563eb", | |
| "extension_proxy": "#d97706", | |
| "neural_block": "#7c3aed", | |
| "unclassified": "#64748b", | |
| } | |
| def _local_path(filename: str) -> Path: | |
| if LOCAL_DATA_DIR: | |
| return Path(LOCAL_DATA_DIR) / Path(filename).name | |
| return Path(__file__).resolve().parent.parent / "TSDecompose-Benchmark" / filename | |
| def _download(filename: str) -> str: | |
| local_candidate = _local_path(filename) | |
| if local_candidate.exists(): | |
| return str(local_candidate) | |
| token = os.getenv("HF_TOKEN") or None | |
| return hf_hub_download( | |
| repo_id=DATASET_REPO, | |
| repo_type="dataset", | |
| filename=filename, | |
| token=token, | |
| ) | |
| def metric_metadata() -> dict: | |
| with open(_download(SITE_FILES["metrics"]), encoding="utf-8") as handle: | |
| return json.load(handle) | |
| def _metric_label(metric: str) -> str: | |
| return COLUMN_LABELS.get(metric, metric) | |
| def _table_label(column: str) -> str: | |
| return TABLE_LABELS.get(column, COLUMN_LABELS.get(column, column)) | |
| def metrics_html() -> str: | |
| meta = metric_metadata() | |
| metrics = meta.get("metrics", []) | |
| cards = [] | |
| for item in metrics: | |
| direction = "higher is better" if item.get("direction") == "higher" else "lower is better" | |
| cards.append( | |
| f""" | |
| <div class="metric-card"> | |
| <div class="metric-name">{escape(item.get("display_name", ""))}</div> | |
| <div class="metric-direction">{escape(direction)}</div> | |
| <code>{escape(item.get("formula", ""))}</code> | |
| <p>{escape(item.get("description", ""))}</p> | |
| </div> | |
| """ | |
| ) | |
| return f""" | |
| <section class="metric-panel"> | |
| <div class="metric-title">Paper Evaluation Protocol</div> | |
| <p> | |
| The classic paper benchmark uses <strong>{int(meta.get("paper_core_metric_count", 5))} core component-recovery metrics</strong>. | |
| Each metric is computed per generated draw after output alignment, then averaged by scenario, tier, or regime. | |
| </p> | |
| <p> | |
| <strong>Table 2</strong> displays only <strong>{int(meta.get("paper_table2_display_metric_count", 2))}</strong> | |
| of those metrics: Trend R2 and Seasonal spectral correlation, split over stationary regimes (Tiers 1-2) | |
| and the non-stationary regime (Tier 3). Its stationary columns use a tier-balanced average of Tier 1 | |
| and Tier 2 means over valid metric values; non-stationary columns use Tier 3 means. <strong>Figure 3</strong> is the five-metric profile view. | |
| </p> | |
| <div class="metric-grid">{''.join(cards)}</div> | |
| </section> | |
| """ | |
| def _method_table() -> pd.DataFrame: | |
| path = _download(SITE_FILES["methods"]) | |
| methods = pd.read_json(path) | |
| return methods[["method", "display_name", "family"]].drop_duplicates("method") | |
| def _paper_family_table() -> pd.DataFrame: | |
| df = pd.read_csv(_download(SITE_FILES["paper_global"])) | |
| df = _numeric(df) | |
| df["display_name"] = df["method_family"] | |
| df["method"] = df["method_family"] | |
| df["family"] = "paper family" | |
| return df | |
| def _numeric(df: pd.DataFrame) -> pd.DataFrame: | |
| out = df.copy() | |
| for col in out.columns: | |
| if col not in {"method", "display_name", "family", "dataset", "round_id"}: | |
| try: | |
| out[col] = pd.to_numeric(out[col]) | |
| except (TypeError, ValueError): | |
| pass | |
| return out | |
| def _attach_methods(df: pd.DataFrame, methods: pd.DataFrame) -> pd.DataFrame: | |
| out = df.merge(methods, on="method", how="left") | |
| out["display_name"] = out["display_name"].fillna(out["method"]) | |
| out["family"] = out["family"].fillna("unclassified") | |
| return out | |
| def _add_mean_rank(df: pd.DataFrame, metrics: list[str]) -> pd.DataFrame: | |
| out = df.copy() | |
| rank_cols = [] | |
| for metric in metrics: | |
| if metric not in out.columns: | |
| continue | |
| values = pd.to_numeric(out[metric], errors="coerce") | |
| if values.notna().sum() == 0: | |
| continue | |
| ascending = not HIGHER_IS_BETTER.get(metric, True) | |
| col = f"rank_{metric}" | |
| out[col] = values.rank(ascending=ascending, method="min") | |
| rank_cols.append(col) | |
| if rank_cols: | |
| out["mean_metric_rank"] = out[rank_cols].mean(axis=1).round(3) | |
| out["rank"] = out["mean_metric_rank"].rank(ascending=True, method="min").astype(int) | |
| return out | |
| def _attach_released_rank(df: pd.DataFrame, rank_file: str) -> pd.DataFrame: | |
| rank = pd.read_csv(_download(rank_file)) | |
| rank = _numeric(rank) | |
| keep = [ | |
| col | |
| for col in ["method", "overall_mean_rank", "overall_std_rank", "setting_count", "rank_order"] | |
| if col in rank.columns | |
| ] | |
| out = df.drop(columns=[col for col in keep if col != "method" and col in df.columns]) | |
| out = out.merge(rank[keep], on="method", how="left") | |
| if "rank_order" in out.columns: | |
| out["rank"] = pd.to_numeric(out["rank_order"], errors="coerce") | |
| elif "overall_mean_rank" in out.columns: | |
| values = pd.to_numeric(out["overall_mean_rank"], errors="coerce") | |
| out["rank"] = values.rank(ascending=True, method="min") | |
| return out | |
| def load_data() -> dict[str, pd.DataFrame]: | |
| methods = _method_table() | |
| paper_global = _paper_family_table() | |
| synthetic = pd.read_csv(_download(SITE_FILES["synthetic"])) | |
| synthetic = _attach_methods(_numeric(synthetic), methods) | |
| synthetic = _attach_released_rank(synthetic, SITE_FILES["synthetic_rank"]) | |
| real_proxy = pd.read_csv(_download(SITE_FILES["real_proxy"])) | |
| real_proxy = _attach_methods(_numeric(real_proxy), methods) | |
| real_proxy["rank"] = pd.to_numeric(real_proxy["rank_order"], errors="coerce") | |
| semisynth = pd.read_csv(_download(SITE_FILES["semisynth"])) | |
| semisynth = _attach_methods(_numeric(semisynth), methods) | |
| semisynth = _attach_released_rank(semisynth, SITE_FILES["semisynth_rank"]) | |
| track_b = pd.read_csv(_download(SITE_FILES["track_b"])) | |
| track_b = _attach_methods(_numeric(track_b), methods) | |
| track_c = pd.read_csv(_download(SITE_FILES["track_c"])) | |
| track_c = _attach_methods(_numeric(track_c), methods) | |
| return { | |
| "paper_global": paper_global, | |
| "synthetic": synthetic, | |
| "real_proxy": real_proxy, | |
| "semisynth": semisynth, | |
| "track_b": track_b, | |
| "track_c": track_c, | |
| } | |
| def available_metrics(view: str) -> list[str]: | |
| df = load_data()[view] | |
| return [ | |
| col | |
| for col in df.columns | |
| if col not in {"method", "display_name", "family", "dataset", "round_id"} | |
| and pd.api.types.is_numeric_dtype(df[col]) | |
| ] | |
| def metric_choices(view: str) -> list[tuple[str, str]]: | |
| return [(_metric_label(metric), metric) for metric in available_metrics(view)] | |
| def filter_table(view: str, metric: str, family: str, query: str, top_n: int): | |
| data = load_data() | |
| df = data[view].copy() | |
| metrics = available_metrics(view) | |
| if metric not in metrics: | |
| metric = DEFAULT_METRIC[view] | |
| if family != "All": | |
| df = df[df["family"] == family] | |
| if query.strip(): | |
| q = query.strip().lower() | |
| text_cols = [col for col in ["method", "display_name", "family", "dataset"] if col in df.columns] | |
| mask = pd.Series(False, index=df.index) | |
| for col in text_cols: | |
| mask = mask | df[col].astype(str).str.lower().str.contains(q, regex=False) | |
| df = df[mask] | |
| ascending = not HIGHER_IS_BETTER.get(metric, True) | |
| if view == "paper_global": | |
| ranked = df.head(int(top_n)) | |
| else: | |
| ranked = df.sort_values(metric, ascending=ascending, na_position="last").head(int(top_n)) | |
| if view == "paper_global": | |
| show_cols = ["display_name", metric] | |
| show_cols += [col for col in PAPER_GLOBAL_METRICS if col != metric] | |
| remaining_metrics = [] | |
| else: | |
| show_cols = [] | |
| for col in [ | |
| "rank", | |
| "display_name", | |
| "family", | |
| "dataset", | |
| metric, | |
| "method", | |
| "mean_metric_rank", | |
| "overall_mean_rank", | |
| "coverage_success", | |
| "row_count", | |
| "setting_count", | |
| ]: | |
| if col in ranked.columns and col not in show_cols: | |
| show_cols.append(col) | |
| remaining_metrics = [ | |
| col | |
| for col in ranked.columns | |
| if col not in show_cols | |
| and col not in {"rank_order"} | |
| and pd.api.types.is_numeric_dtype(ranked[col]) | |
| ] | |
| table = ranked[show_cols + remaining_metrics].copy() | |
| table = table.round(4) | |
| table_html = _table_html(table) | |
| plot_df = ranked.copy() | |
| if "dataset" in plot_df.columns and plot_df["method"].duplicated().any(): | |
| plot_df = ( | |
| plot_df.groupby(["method", "display_name", "family"], as_index=False)[metric] | |
| .mean(numeric_only=True) | |
| .sort_values(metric, ascending=ascending) | |
| .head(int(top_n)) | |
| ) | |
| metric_name = _metric_label(metric) | |
| fig = _bar_figure(plot_df, metric, metric_name, view) | |
| summary = ( | |
| f"**{VIEW_LABELS[view]}** \n" | |
| f"{VIEW_NOTES[view]} \n\n" | |
| f"Rows shown: `{len(table)}` from `{len(data[view])}`. " | |
| f"Metric: `{metric_name}`. " | |
| f"Metric direction: `{'higher is better' if HIGHER_IS_BETTER.get(metric, True) else 'lower is better'}`. " | |
| f"Dataset source: [`{DATASET_REPO}`](https://huggingface.co/datasets/{DATASET_REPO})." | |
| ) | |
| figure = _figure_update(view) | |
| return table_html, fig, summary, figure | |
| def _table_html(table: pd.DataFrame) -> str: | |
| display = table.copy() | |
| if "family" in display.columns: | |
| display["family"] = display["family"].replace( | |
| {"extension_proxy": "extension proxy"} | |
| ) | |
| display = display.rename(columns={col: _table_label(col) for col in display.columns}) | |
| html = display.to_html(index=False, escape=False, classes="leaderboard-table") | |
| return f""" | |
| <div class="table-title">Leaderboard table</div> | |
| <div class="table-wrap">{html}</div> | |
| """ | |
| def _bar_figure(plot_df: pd.DataFrame, metric: str, metric_name: str, view: str) -> go.Figure: | |
| data = plot_df.copy() | |
| data[metric] = pd.to_numeric(data[metric], errors="coerce") | |
| data = data[data[metric].notna()] | |
| labels = data["display_name"].astype(str).tolist() | |
| values = [float(value) for value in data[metric].tolist()] | |
| families = data.get("family", pd.Series(["unclassified"] * len(data))).astype(str).tolist() | |
| colors = [FAMILY_COLORS.get(family, "#0f766e") for family in families] | |
| hover = [ | |
| f"Family: {family}<br>Method: {method}" | |
| for family, method in zip(families, data.get("method", data["display_name"]).astype(str)) | |
| ] | |
| fig = go.Figure() | |
| fig.add_bar( | |
| x=values, | |
| y=labels, | |
| orientation="h", | |
| marker=dict(color=colors, line=dict(color="#1f2937", width=0.5)), | |
| customdata=hover, | |
| hovertemplate="%{y}<br>" + escape(metric_name) + ": %{x:.4f}<br>%{customdata}<extra></extra>", | |
| ) | |
| title = f"{VIEW_LABELS[view]} - {metric_name}" | |
| fig.update_layout( | |
| title=dict(text=title, x=0.01, xanchor="left"), | |
| height=max(360, 34 * max(len(labels), 6)), | |
| margin=dict(l=150, r=24, t=56, b=48), | |
| yaxis=dict( | |
| autorange="reversed", | |
| title="", | |
| type="category", | |
| categoryorder="array", | |
| categoryarray=labels, | |
| tickfont=dict(size=13), | |
| ), | |
| xaxis=dict(title=metric_name, zeroline=True, rangemode="tozero"), | |
| showlegend=False, | |
| paper_bgcolor="white", | |
| plot_bgcolor="white", | |
| bargap=0.28, | |
| ) | |
| return fig | |
| def _figure_update(view: str): | |
| if view != "paper_global": | |
| return gr.update(value=None, visible=False) | |
| bundled = Path(__file__).resolve().parent / "assets" / "selected_radar_charts.png" | |
| if bundled.exists(): | |
| return gr.update(value=str(bundled), visible=True) | |
| return gr.update(value=_download(SITE_FILES["paper_radar"]), visible=True) | |
| def refresh_controls(view: str): | |
| metrics = available_metrics(view) | |
| return gr.update(choices=metric_choices(view), value=DEFAULT_METRIC[view]), gr.update(value="All") | |
| def stats_html() -> str: | |
| data = load_data() | |
| paper_families = data["paper_global"]["method"].nunique() | |
| synthetic_methods = data["synthetic"]["method"].nunique() | |
| return f""" | |
| <div class="stats-grid"> | |
| <div class="stat"><span class="stat-value">{paper_families}</span><span class="stat-label">Paper method families</span></div> | |
| <div class="stat"><span class="stat-value">5</span><span class="stat-label">Paper core metrics</span></div> | |
| <div class="stat"><span class="stat-value">6 x 50</span><span class="stat-label">Classic synthetic instances</span></div> | |
| <div class="stat"><span class="stat-value">{synthetic_methods}</span><span class="stat-label">Expansion method rows</span></div> | |
| <div class="stat"><span class="stat-value">2</span><span class="stat-label">Evidence versions</span></div> | |
| </div> | |
| """ | |
| CSS = """ | |
| .gradio-container { max-width: 1220px !important; } | |
| .stats-grid { | |
| display: grid; | |
| grid-template-columns: repeat(5, minmax(0, 1fr)); | |
| gap: 10px; | |
| margin: 10px 0 16px; | |
| } | |
| .stat { | |
| border: 1px solid #d8dee9; | |
| border-radius: 8px; | |
| padding: 12px 14px; | |
| background: #ffffff; | |
| } | |
| .stat-value { | |
| display: block; | |
| font-size: 24px; | |
| font-weight: 700; | |
| color: #0f3b57; | |
| } | |
| .stat-label { | |
| display: block; | |
| font-size: 13px; | |
| color: #53606f; | |
| margin-top: 2px; | |
| } | |
| .metric-panel { | |
| border: 1px solid #d8dee9; | |
| border-radius: 8px; | |
| padding: 14px; | |
| background: #ffffff; | |
| margin: 0 0 16px; | |
| } | |
| .metric-title { | |
| font-size: 16px; | |
| font-weight: 700; | |
| color: #1f2937; | |
| margin-bottom: 6px; | |
| } | |
| .metric-panel p { | |
| color: #374151; | |
| font-size: 14px; | |
| margin: 6px 0; | |
| } | |
| .metric-grid { | |
| display: grid; | |
| grid-template-columns: repeat(5, minmax(0, 1fr)); | |
| gap: 8px; | |
| margin-top: 12px; | |
| } | |
| .metric-card { | |
| border: 1px solid #e5e9f0; | |
| border-radius: 8px; | |
| padding: 10px; | |
| background: #fbfcfd; | |
| min-width: 0; | |
| } | |
| .metric-name { | |
| font-size: 13px; | |
| font-weight: 700; | |
| color: #1f2937; | |
| } | |
| .metric-direction { | |
| font-size: 12px; | |
| color: #53606f; | |
| margin: 3px 0 6px; | |
| } | |
| .metric-card code { | |
| display: block; | |
| white-space: normal; | |
| overflow-wrap: anywhere; | |
| background: #f3f5f8; | |
| border-radius: 6px; | |
| padding: 6px; | |
| font-size: 11px; | |
| color: #273142; | |
| } | |
| .metric-card p { | |
| font-size: 12px; | |
| margin: 7px 0 0; | |
| } | |
| .table-title { | |
| font-size: 14px; | |
| color: #303946; | |
| margin: 12px 0 8px; | |
| } | |
| .table-wrap { | |
| overflow-x: auto; | |
| border: 1px solid #d8dee9; | |
| border-radius: 8px; | |
| background: #ffffff; | |
| max-height: 520px; | |
| } | |
| .leaderboard-table { | |
| border-collapse: collapse; | |
| width: max-content; | |
| min-width: 100%; | |
| font-size: 13px; | |
| } | |
| .leaderboard-table th, | |
| .leaderboard-table td { | |
| border-bottom: 1px solid #edf0f4; | |
| padding: 8px 10px; | |
| text-align: left; | |
| white-space: nowrap; | |
| } | |
| .leaderboard-table th { | |
| position: sticky; | |
| top: 0; | |
| background: #f7f9fb; | |
| color: #1f2937; | |
| z-index: 1; | |
| } | |
| .leaderboard-table th:nth-child(1), | |
| .leaderboard-table td:nth-child(1) { | |
| min-width: 48px; | |
| } | |
| .leaderboard-table th:nth-child(2), | |
| .leaderboard-table td:nth-child(2) { | |
| min-width: 210px; | |
| } | |
| .leaderboard-table th:nth-child(3), | |
| .leaderboard-table td:nth-child(3) { | |
| min-width: 130px; | |
| } | |
| .leaderboard-table tr:nth-child(even) td { | |
| background: #fbfcfd; | |
| } | |
| @media (max-width: 780px) { | |
| .stats-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } | |
| .metric-grid { grid-template-columns: repeat(1, minmax(0, 1fr)); } | |
| } | |
| """ | |
| GRADIO_MAJOR = int(gr.__version__.split(".", 1)[0]) | |
| BLOCKS_KWARGS = {"title": "TSDecompose Benchmark Leaderboard"} | |
| if GRADIO_MAJOR < 6: | |
| BLOCKS_KWARGS["css"] = CSS | |
| with gr.Blocks(**BLOCKS_KWARGS) as demo: | |
| gr.Markdown( | |
| """ | |
| # TSDecompose Benchmark Leaderboard | |
| Interactive leaderboard for **Time-Series Decomposition as a Standalone Task: A Mechanism-Identifiable Benchmark**. | |
| The dataset repository is the source of record; this Space is the web presentation layer. | |
| """ | |
| ) | |
| gr.HTML(value=stats_html()) | |
| gr.HTML(value=metrics_html()) | |
| with gr.Row(): | |
| view = gr.Dropdown( | |
| choices=[(label, key) for key, label in VIEW_LABELS.items()], | |
| value="paper_global", | |
| label="View", | |
| ) | |
| metric = gr.Dropdown( | |
| choices=metric_choices("paper_global"), | |
| value=DEFAULT_METRIC["paper_global"], | |
| label="Metric", | |
| ) | |
| with gr.Row(): | |
| family = gr.Dropdown( | |
| choices=["All", "paper family", "classical", "extension_proxy"], | |
| value="All", | |
| label="Family", | |
| ) | |
| query = gr.Textbox(label="Search", placeholder="method, family, or dataset") | |
| top_n = gr.Slider(5, 40, value=22, step=1, label="Rows") | |
| note = gr.Markdown() | |
| figure_ref = gr.Image( | |
| label="Paper Figure 3 reference", | |
| type="filepath", | |
| interactive=False, | |
| visible=True, | |
| ) | |
| plot = gr.Plot() | |
| table = gr.HTML() | |
| inputs = [view, metric, family, query, top_n] | |
| for control in inputs: | |
| control.change(filter_table, inputs=inputs, outputs=[table, plot, note, figure_ref]) | |
| view.change(refresh_controls, inputs=view, outputs=[metric, family]).then( | |
| filter_table, inputs=inputs, outputs=[table, plot, note, figure_ref] | |
| ) | |
| demo.load(filter_table, inputs=inputs, outputs=[table, plot, note, figure_ref]) | |
| if __name__ == "__main__": | |
| if GRADIO_MAJOR >= 6: | |
| demo.launch(css=CSS) | |
| else: | |
| demo.launch() | |