LiveHouse-TS / src /utils.py
ziyuzhou02's picture
Deploy Space: update src
1458007 verified
Raw
History Blame Contribute Delete
53.5 kB
from __future__ import annotations
import json
import os
import re
from datetime import datetime, timedelta, timezone
from pathlib import Path
import numpy as np
import pandas as pd
from scipy import stats
from src.display.formatting import format_timestamp_utc8
from src.rank_based_metric import compute_rank_score
METRIC_COLUMNS = [
"eval_metrics/MSE[mean]",
"eval_metrics/MSE[0.5]",
"eval_metrics/MAE[0.5]",
"eval_metrics/MASE[0.5]",
"eval_metrics/MAPE[0.5]",
"eval_metrics/sMAPE[0.5]",
"eval_metrics/MSIS",
"eval_metrics/RMSE[mean]",
"eval_metrics/NRMSE[mean]",
"eval_metrics/ND[0.5]",
"eval_metrics/mean_weighted_sum_quantile_loss",
]
DISPLAY_METRICS = [
"eval_metrics/MSE[mean]",
"eval_metrics/mean_weighted_sum_quantile_loss",
"eval_metrics/MASE[0.5]",
"eval_metrics/MAE[0.5]",
"eval_metrics/RMSE[mean]",
"eval_metrics/sMAPE[0.5]",
"eval_metrics/MSIS",
"eval_metrics/ND[0.5]",
"eval_metrics/NRMSE[mean]",
"eval_metrics/MAPE[0.5]",
]
METRIC_LABELS = {
"eval_metrics/MSE[mean]": "MSE",
"eval_metrics/MASE[0.5]": "MASE",
"eval_metrics/mean_weighted_sum_quantile_loss": "CRPS",
"eval_metrics/MAE[0.5]": "MAE",
"eval_metrics/RMSE[mean]": "RMSE",
"eval_metrics/sMAPE[0.5]": "sMAPE",
"eval_metrics/MSIS": "MSIS",
"eval_metrics/ND[0.5]": "ND",
"eval_metrics/NRMSE[mean]": "NRMSE",
"eval_metrics/MAPE[0.5]": "MAPE",
}
RANK_SCORE_COLUMN = "RankScore"
RANK_SCORE_RANK_COLUMN = "RankScore_Rank"
RANK_SCORE_BASE_METRICS = [
"eval_metrics/MSE[mean]",
"eval_metrics/mean_weighted_sum_quantile_loss",
]
GEOMEAN_EPSILON = 1e-12
VALUE_COLUMNS = ["MSE", "CRPS", RANK_SCORE_COLUMN] + [
METRIC_LABELS[m] for m in DISPLAY_METRICS if METRIC_LABELS[m] not in {"MSE", "CRPS"}
]
RANK_COLUMNS = ["MSE_Rank", "CRPS_Rank", RANK_SCORE_RANK_COLUMN] + [
f"{METRIC_LABELS[m]}_Rank"
for m in DISPLAY_METRICS
if METRIC_LABELS[m] not in {"MSE", "CRPS"}
]
LEGACY_MODEL_NAMES = {
"TSFM1": "Chronos-Bolt-Tiny",
"TSFM2": "Chronos-Bolt-Base",
"TSFM3": "Chronos-2",
}
RESULT_COLUMNS = [
"dataset",
"model",
*METRIC_COLUMNS,
"domain",
"num_variates",
]
CANONICAL_DOMAINS = [
"Finance",
"Climate",
"Ocean",
"Hydrology",
"Transportation",
"Web",
"Hazards",
]
DOMAIN_DISPLAY = {
"Finance": "Finance",
"Econ/Fin": "Finance",
"Sales": "Finance",
"Climate": "Climate",
"Weather": "Climate",
"Air Quality": "Climate",
"Nature": "Climate",
"Energy": "Climate",
"Ocean": "Ocean",
"Hydrology": "Hydrology",
"Transport": "Transportation",
"Transportation": "Transportation",
"Web": "Web",
"Web/CloudOps": "Web",
"Hazards": "Hazards",
"Healthcare": "Hazards",
}
def _read_result_csv(path: str | Path) -> pd.DataFrame:
df = pd.read_csv(
path,
usecols=lambda column: column in RESULT_COLUMNS,
index_col=False,
)
missing = [column for column in RESULT_COLUMNS if column not in df.columns]
if missing:
raise ValueError(f"{path} is missing required result columns: {missing}")
return df
def _dataset_key(value: object) -> str:
return str(value).split("/")[0].strip().lower()
def _model_key(value: object) -> str:
return re.sub(r"[^a-z0-9]+", "", str(value).lower())
def _load_model_allowlist(root_dir: str | Path) -> set[str]:
path = Path(root_dir) / "model_allowlist.json"
if not path.exists():
return set()
try:
payload = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
return set()
models = payload.get("models", payload) if isinstance(payload, dict) else payload
if not isinstance(models, list):
return set()
return {_model_key(model) for model in models if str(model).strip()}
def _load_live_dataset_keys(ds_properties: str | Path | None) -> set[str]:
if ds_properties is None:
return set()
path = Path(ds_properties)
if not path.exists():
return set()
try:
props = pd.read_csv(path)
except (OSError, pd.errors.ParserError, pd.errors.EmptyDataError):
return set()
if "dataset" not in props.columns:
return set()
return {_dataset_key(value) for value in props["dataset"].dropna()}
GIFT_AGGREGATE_FILES = {
"prediction_length": "results_by_prediction_length.csv",
"domain": "results_by_domain.csv",
"frequency": "results_by_frequency.csv",
}
GIFT_AGGREGATE_LABELS = {
"prediction_length": "Prediction Length",
"domain": "Domain",
"frequency": "Frequency",
}
LIVE_AGGREGATE_FILES = {
"overall": "live_overall_up_to_now.csv",
"rank": "live_rank_up_to_now.csv",
}
def format_number(value):
"""Format a displayed leaderboard metric without hiding tiny non-zero values."""
if pd.isna(value):
return "n/a"
number = float(value)
if not np.isfinite(number):
return "n/a"
if number != 0.0 and abs(number) < 0.005:
return f"{number:.2e}"
return f"{number:.2f}"
def format_rank_number(value):
"""Format rank-like columns as positive integers for leaderboard display."""
if pd.isna(value):
return "n/a"
number = float(value)
if not np.isfinite(number):
return "n/a"
return str(max(1, int(round(number))))
def _is_rank_display_column(column: object) -> bool:
name = str(column)
return "Rank" in name and name != RANK_SCORE_COLUMN
def aggregate_gmean(series: pd.Series) -> float:
values = pd.to_numeric(series, errors="coerce").dropna()
values = values[np.isfinite(values) & (values >= 0.0)]
if values.empty:
return np.nan
values = values.clip(lower=GEOMEAN_EPSILON)
return float(stats.gmean(values))
def aggregate_rank_mean(series: pd.Series) -> float:
return float(series.mean(skipna=True))
def format_df(df: pd.DataFrame) -> pd.DataFrame:
formatted = df.copy()
for col in formatted.columns:
if pd.api.types.is_numeric_dtype(formatted[col]):
formatter = format_rank_number if _is_rank_display_column(col) else format_number
formatted[col] = formatted[col].map(formatter)
return formatted
def unify_freq(df: pd.DataFrame) -> pd.DataFrame:
df = df.copy()
df["frequency"] = df["frequency"].str.replace(r"\d+", "", regex=True)
df["frequency"] = df["frequency"].str.split("-").str[0]
return df
def load_all_results(root_dir: str = "results") -> pd.DataFrame:
frames = []
for subdir, _, files in os.walk(root_dir):
for file in files:
if file != "all_results.csv":
continue
frame = _read_result_csv(os.path.join(subdir, file))
if frame.empty:
continue
frames.append(frame)
if not frames:
return pd.DataFrame(columns=RESULT_COLUMNS)
df = pd.concat(frames, ignore_index=True)
df = df.dropna(subset=["dataset"], how="any")
df = df[df["dataset"].astype(str).str.len() > 0]
if df.empty:
return pd.DataFrame(columns=RESULT_COLUMNS)
df = df.sort_values(by=["model", "dataset"]).reset_index(drop=True)
df["model"] = df["model"].replace(LEGACY_MODEL_NAMES)
model_allowlist = _load_model_allowlist(root_dir)
if model_allowlist:
df = df[df["model"].map(_model_key).isin(model_allowlist)].copy()
if df.empty:
return pd.DataFrame(columns=RESULT_COLUMNS)
parts = df["dataset"].astype(str).str.split("/", expand=True)
if parts.shape[1] < 3:
for idx in range(parts.shape[1], 3):
parts[idx] = "short" if idx == 2 else ""
df["dataset"] = parts[0]
df["frequency"] = parts[1]
df["term_length"] = parts[2]
return df
def enrich_with_properties(df: pd.DataFrame, ds_properties_path: str) -> pd.DataFrame:
props = pd.read_csv(ds_properties_path)
props["dataset"] = props["dataset"].str.lower().str.replace(" ", "_")
props["dataset"] = props["dataset"].str.replace("-", "_")
props["dataset"] = props["dataset"].apply(lambda x: re.sub("_+", "_", x).strip("_"))
props_map = props.set_index("dataset").T.to_dict()
for dataset_key, values in props_map.items():
for key, value in values.items():
if key == "frequency" and df.loc[df["dataset"] == dataset_key, "frequency"].notna().all():
continue
df.loc[df["dataset"] == dataset_key, key] = value
return unify_freq(df)
def prepare_results_df(
root_dir: str = "results",
ds_properties: str | None = None,
) -> pd.DataFrame:
if ds_properties is None:
ds_properties = str(Path(root_dir) / "dataset_properties.csv")
df = load_all_results(root_dir)
if df.empty:
return df
live_dataset_keys = _load_live_dataset_keys(ds_properties)
if live_dataset_keys:
df = df[df["dataset"].map(_dataset_key).isin(live_dataset_keys)].copy()
if df.empty:
return pd.DataFrame(columns=[*RESULT_COLUMNS, "frequency", "term_length"])
if Path(ds_properties).exists():
df = enrich_with_properties(df, ds_properties)
if "domain" in df.columns:
df["domain"] = df["domain"].map(display_domain)
for metric in METRIC_COLUMNS:
df[metric] = pd.to_numeric(df[metric], errors="coerce")
df.loc[~np.isfinite(df[metric]), metric] = np.nan
return _add_per_dataset_ranks(df)
def _add_per_dataset_ranks(df: pd.DataFrame) -> pd.DataFrame:
if df.empty:
return df
df = df.copy()
group_keys = ["dataset", "term_length", "frequency"]
for metric in DISPLAY_METRICS:
rank_col = f"Rank_{metric}"
df[rank_col] = df.groupby(group_keys)[metric].rank(method="first", ascending=True)
return df
def _empty_grouped_dfs() -> dict[str, pd.DataFrame | list[str]]:
rank_cols = [f"Rank_{metric}" for metric in DISPLAY_METRICS]
return {
"overall_values": pd.DataFrame(columns=DISPLAY_METRICS),
"overall_ranks": pd.DataFrame(columns=rank_cols),
"dataset_values": {},
"dataset_ranks": {},
"datasets": [],
}
def get_grouped_dfs(
root_dir: str = "results",
ds_properties: str | None = None,
) -> dict[str, pd.DataFrame | list[str]]:
df = prepare_results_df(root_dir, ds_properties)
if df.empty:
return _empty_grouped_dfs()
rank_cols = [f"Rank_{metric}" for metric in DISPLAY_METRICS]
overall_values = df.groupby(["model"])[DISPLAY_METRICS].agg(aggregate_gmean)
overall_ranks = df.groupby(["model"])[rank_cols].agg(aggregate_rank_mean)
overall_values = overall_values.rename(columns=METRIC_LABELS)
overall_ranks = overall_ranks.rename(
columns={f"Rank_{metric}": f"{METRIC_LABELS[metric]}_Rank" for metric in DISPLAY_METRICS}
)
rank_score = compute_rank_score(
df,
metric_columns=RANK_SCORE_BASE_METRICS,
group_columns=("dataset", "term_length", "frequency"),
)
overall_values = overall_values.join(rank_score, how="left")
if RANK_SCORE_COLUMN in overall_values.columns:
overall_ranks[RANK_SCORE_RANK_COLUMN] = overall_values[RANK_SCORE_COLUMN].rank(
method="average",
ascending=False,
)
datasets = sorted(df["dataset"].dropna().unique())
dataset_values: dict[str, pd.DataFrame] = {}
dataset_ranks: dict[str, pd.DataFrame] = {}
for dataset in datasets:
subset = df[df["dataset"] == dataset]
if subset.empty:
continue
values = subset.groupby("model")[DISPLAY_METRICS].agg(aggregate_gmean).rename(
columns=METRIC_LABELS
)
ranks = subset.groupby("model")[rank_cols].agg(aggregate_rank_mean).rename(
columns={f"Rank_{metric}": f"{METRIC_LABELS[metric]}_Rank" for metric in DISPLAY_METRICS}
)
dataset_values[dataset] = values
dataset_ranks[dataset] = ranks
return {
"overall_values": overall_values,
"overall_ranks": overall_ranks,
"dataset_values": dataset_values,
"dataset_ranks": dataset_ranks,
"datasets": datasets,
}
def prepare_values_table(values_df: pd.DataFrame) -> pd.DataFrame:
if values_df is None or values_df.empty:
return pd.DataFrame(columns=["Model"] + VALUE_COLUMNS)
table = values_df.reset_index().rename(columns={"model": "Model"})
cols = ["Model"] + [col for col in VALUE_COLUMNS if col in table.columns]
table = table[cols]
if "MSE" in table.columns and not table["MSE"].isna().all():
table = table.sort_values(by=["MSE"])
return format_df(table)
if "RankScore" in table.columns and not table["RankScore"].isna().all():
table = table.sort_values(by=["RankScore"], ascending=False)
return format_df(table)
def prepare_ranks_table(ranks_df: pd.DataFrame) -> pd.DataFrame:
if ranks_df is None or ranks_df.empty:
return pd.DataFrame(columns=["Model"] + RANK_COLUMNS)
table = ranks_df.reset_index().rename(columns={"model": "Model"})
cols = ["Model"] + [col for col in RANK_COLUMNS if col in table.columns]
table = table[cols]
if "MSE_Rank" in table.columns and not table["MSE_Rank"].isna().all():
table = table.sort_values(by=["MSE_Rank"])
return format_df(table)
if RANK_SCORE_RANK_COLUMN in table.columns and not table[RANK_SCORE_RANK_COLUMN].isna().all():
table = table.sort_values(by=[RANK_SCORE_RANK_COLUMN])
return format_df(table)
def load_gift_aggregate_table(
root_dir: str = "results",
dimension: str = "prediction_length",
) -> pd.DataFrame:
"""Load one persisted GIFT-Eval-style grouped result table."""
if dimension not in GIFT_AGGREGATE_FILES:
raise ValueError(f"Unknown aggregate dimension: {dimension}")
label = GIFT_AGGREGATE_LABELS[dimension]
columns = [label, "Model", "MSE", "CRPS", "Rank", "Configs", "Coverage"]
path = Path(root_dir) / "aggregates" / GIFT_AGGREGATE_FILES[dimension]
if not path.exists():
return pd.DataFrame(columns=columns)
try:
table = pd.read_csv(path)
except (OSError, pd.errors.ParserError, pd.errors.EmptyDataError):
return pd.DataFrame(columns=columns)
required = {
dimension,
"model",
"MSE",
"CRPS",
"Rank",
"n_configs",
"n_group_configs",
"coverage",
}
if table.empty or not required.issubset(table.columns):
return pd.DataFrame(columns=columns)
table = table.sort_values([dimension, "Rank", "model"]).copy()
table["Configs"] = (
table["n_configs"].fillna(0).astype(int).astype(str)
+ "/"
+ table["n_group_configs"].fillna(0).astype(int).astype(str)
)
table["Coverage"] = table["coverage"].map(
lambda value: "n/a" if pd.isna(value) else f"{100.0 * float(value):.2f}%"
)
for metric in ("MSE", "CRPS"):
table[metric] = pd.to_numeric(table[metric], errors="coerce").map(format_number)
table["Rank"] = pd.to_numeric(table["Rank"], errors="coerce").map(format_rank_number)
table = table.rename(columns={dimension: label, "model": "Model"})
return table.loc[:, columns].reset_index(drop=True)
def load_gift_aggregate_metadata_md(root_dir: str = "results") -> str:
path = Path(root_dir) / "aggregates" / "metadata.json"
if not path.exists():
return "Aggregate metadata is not available yet."
try:
metadata = json.loads(path.read_text())
except (OSError, json.JSONDecodeError):
return "Aggregate metadata could not be loaded."
generated = format_timestamp_utc8(metadata.get("generated_at", ""))
return (
f"**Protocol:** per-configuration normalization against "
f"`{metadata.get('baseline_model', 'Seasonal-Naive')}`; geometric mean for "
f"MSE/CRPS; mean CRPS rank. **Coverage:** "
f"{metadata.get('num_models', 0)} models × {metadata.get('num_configs', 0)} "
f"configurations. **Generated:** {generated}."
)
def load_live_aggregate_table(
root_dir: str = "results",
table: str = "overall",
) -> pd.DataFrame:
"""Load the cumulative future-release metric or rank table."""
if table not in LIVE_AGGREGATE_FILES:
raise ValueError(f"Unknown live aggregate table: {table}")
if table == "overall":
columns = [
"Rank",
"Model",
"MSE",
"RMSE",
"MAPE",
"CRPS",
"RTG",
"Stability",
"Improvement",
"Datasets",
"Releases",
]
else:
columns = [
"Rank",
"Status",
"Model",
"Win Rate",
"Eligible Opponents",
"Shared Releases",
"Shared Datasets",
"Covered Span (days)",
]
path = Path(root_dir) / "aggregates" / LIVE_AGGREGATE_FILES[table]
if not path.exists():
return pd.DataFrame(columns=columns)
try:
frame = pd.read_csv(path)
except (OSError, pd.errors.ParserError, pd.errors.EmptyDataError):
return pd.DataFrame(columns=columns)
if frame.empty:
return pd.DataFrame(columns=columns)
if table == "overall":
source_columns = [
"Rank",
"model",
"MSE",
"RMSE",
"MAPE",
"CRPS",
"RTG",
"Stability",
"Improvement",
"n_datasets",
"n_releases",
]
if not set(source_columns).issubset(frame.columns):
return pd.DataFrame(columns=columns)
frame = frame[source_columns].rename(
columns={"model": "Model", "n_datasets": "Datasets", "n_releases": "Releases"}
)
for metric in ("MSE", "RMSE", "MAPE", "CRPS", "RTG", "Stability", "Improvement"):
frame[metric] = pd.to_numeric(frame[metric], errors="coerce").map(format_number)
frame["Rank"] = pd.to_numeric(frame["Rank"], errors="coerce").map(format_rank_number)
else:
source_columns = [
"Rank",
"Status",
"model",
"WinRate",
"eligible_opponents",
"shared_releases",
"shared_datasets",
"covered_span_days",
]
if not set(source_columns).issubset(frame.columns):
return pd.DataFrame(columns=columns)
frame = frame[source_columns].rename(
columns={
"model": "Model",
"WinRate": "Win Rate",
"eligible_opponents": "Eligible Opponents",
"shared_releases": "Shared Releases",
"shared_datasets": "Shared Datasets",
"covered_span_days": "Covered Span (days)",
}
)
frame["Rank"] = pd.to_numeric(frame["Rank"], errors="coerce").map(format_rank_number)
frame["Win Rate"] = pd.to_numeric(frame["Win Rate"], errors="coerce").map(
lambda value: "n/a" if pd.isna(value) else f"{100.0 * float(value):.2f}%"
)
frame["Covered Span (days)"] = pd.to_numeric(
frame["Covered Span (days)"], errors="coerce"
).map(format_number)
return frame.loc[:, columns].reset_index(drop=True)
def load_live_aggregate_metadata_md(root_dir: str = "results") -> str:
path = Path(root_dir) / "aggregates" / "live_metadata.json"
if not path.exists():
return "Cumulative live metrics are not available yet."
try:
metadata = json.loads(path.read_text())
except (OSError, json.JSONDecodeError):
return "Cumulative live metric metadata could not be loaded."
generated = format_timestamp_utc8(metadata.get("generated_at", ""))
return (
f"**Up to now:** {metadata.get('num_models', 0)} models, "
f"{metadata.get('num_datasets', 0)} datasets, and "
f"{metadata.get('num_unique_releases', 0)} distinct future releases. "
"Metrics are stored without forecasts or ground truth; RTG is relative to "
"matched Seasonal-Naive MSE. **Generated:** "
f"{generated}."
)
DATASET_SHORT_NAMES = {
"binance_btcusdt_1h_close": "BTC/USDT",
"coingecko_bitcoin_7d_price_usd": "Bitcoin USD",
"nasa_power_shanghai_hourly_t2m": "Shanghai temperature (NASA POWER)",
"nasa_power_shanghai_hourly_T2M": "Shanghai temperature (NASA POWER)",
"open_meteo_shanghai_hourly_temperature_2m": "Shanghai temperature",
"open_meteo_shanghai_air_quality_hourly_pm2_5": "Shanghai PM2.5",
"noaa_coops_sf_water_level_water_level": "SF Bay water level",
"noaa_ndbc_46013_realtime_wave_height": "Ocean wave height",
"nws_ksfo_observations_temperature": "KSFO temperature",
"usgs_earthquake_all_week_hourly_earthquake_count": "Earthquake count",
"usgs_potomac_iv_usgs_00060": "Potomac river flow",
"wikimedia_time_series_hourly_pageviews": "Wikipedia pageviews",
"data_gov_sg_singapore_rainfall_5min_rainfall_5min_total": "Singapore rainfall",
"data_gov_sg_singapore_taxi_availability_available_taxi_count": "Singapore available taxis",
"data_gov_sg_sgx_equities_turnover_monthly_turnover_value_million_sgd": "SGX equities turnover",
}
def display_domain(raw_domain: str) -> str:
domain = str(raw_domain).strip()
if domain in DOMAIN_DISPLAY:
return DOMAIN_DISPLAY[domain]
lowered = domain.lower()
if any(token in lowered for token in ("finance", "econ", "sales")):
return "Finance"
if any(token in lowered for token in ("climate", "weather", "air", "nature", "energy")):
return "Climate"
if "ocean" in lowered:
return "Ocean"
if "hydro" in lowered:
return "Hydrology"
if any(token in lowered for token in ("transport", "taxi")):
return "Transportation"
if any(token in lowered for token in ("web", "cloud", "wiki")):
return "Web"
return "Hazards"
def dataset_short_name(dataset: str) -> str:
base = dataset.split("/")[0].strip()
if base in DATASET_SHORT_NAMES:
return DATASET_SHORT_NAMES[base]
label = base.replace("_", " ")
for prefix in ("open meteo ", "noaa coops ", "noaa ndbc ", "nws ", "usgs ", "wikimedia "):
if label.lower().startswith(prefix):
label = label[len(prefix) :]
return label[:40] + ("…" if len(label) > 40 else "")
def lookup_dataset_domain(dataset: str, root_dir: str = "results") -> str:
# Primary: dataset_properties.csv
base = dataset.split("/")[0].strip()
props_path = Path(root_dir) / "dataset_properties.csv"
if props_path.exists():
props = pd.read_csv(props_path)
match = props[props["dataset"].map(_dataset_key) == _dataset_key(base)]
if not match.empty and "domain" in match.columns:
return str(match.iloc[0]["domain"])
# Fallback: read domain column from any all_results.csv
for subdir in Path(root_dir).iterdir():
csv_path = subdir / "all_results.csv"
if not csv_path.exists():
continue
try:
df = _read_result_csv(csv_path)
if "domain" not in df.columns or "dataset" not in df.columns:
continue
# dataset column in all_results is "dataset_id/freq/term", strip suffix
df["_ds_key"] = df["dataset"].astype(str).str.split("/").str[0]
match = df[df["_ds_key"].str.lower() == base.lower()]
if not match.empty:
return str(match.iloc[0]["domain"])
except Exception:
continue
return "Other"
DOMAIN_TAB_ORDER = [
*CANONICAL_DOMAINS,
]
def group_datasets_by_domain(
datasets: list[str],
root_dir: str = "results",
) -> dict[str, list[str]]:
by_domain: dict[str, list[str]] = {}
seen: set[str] = set()
for dataset in datasets:
dataset = dataset.split("/")[0].strip()
if dataset in seen:
continue
seen.add(dataset)
domain = display_domain(lookup_dataset_domain(dataset, root_dir))
by_domain.setdefault(domain, []).append(dataset)
ordered: dict[str, list[str]] = {}
for domain in DOMAIN_TAB_ORDER:
if domain in by_domain:
ordered[domain] = by_domain.pop(domain)
for domain in sorted(by_domain):
ordered[domain] = by_domain[domain]
return ordered
def _parse_timestamp(value: object) -> datetime | None:
if not value or value == "n/a":
return None
try:
return datetime.fromisoformat(str(value).replace("Z", "+00:00"))
except (TypeError, ValueError):
return None
def _read_json(path: Path) -> dict:
if not path.exists():
return {}
try:
return json.loads(path.read_text())
except (OSError, json.JSONDecodeError):
return {}
def leaderboard_refresh_timestamp(root_dir: str = "results") -> str:
"""Return the latest timestamp that can change leaderboard-visible results."""
root = Path(root_dir)
candidates: list[datetime] = []
status = _read_json(root / "online_status.json")
for key in ("finished_at", "pushed_at"):
parsed = _parse_timestamp(status.get(key))
if parsed is not None:
candidates.append(parsed)
for metadata_name in ("metadata.json", "live_metadata.json"):
metadata = _read_json(root / "aggregates" / metadata_name)
parsed = _parse_timestamp(metadata.get("generated_at"))
if parsed is not None:
candidates.append(parsed)
if not candidates:
return "n/a"
return format_timestamp_utc8(max(candidates).isoformat())
def build_leaderboard_summary_html(root_dir: str = "results") -> str:
df = prepare_results_df(root_dir)
if df.empty:
return """
<div class="summary-grid">
<div class="summary-card"><div class="summary-value">0</div><div class="summary-label">Models</div></div>
<div class="summary-card"><div class="summary-value">0</div><div class="summary-label">Datasets</div></div>
<div class="summary-card"><div class="summary-value">0</div><div class="summary-label">Domains</div></div>
<div class="summary-card"><div class="summary-value">n/a</div><div class="summary-label">Last Refresh</div></div>
</div>
"""
models = df["model"].nunique()
datasets = df["dataset"].nunique()
domains = df["domain"].nunique() if "domain" in df.columns else 0
last_refresh = leaderboard_refresh_timestamp(root_dir)
return f"""
<div class="summary-grid">
<div class="summary-card"><div class="summary-value">{models}</div><div class="summary-label">Models</div></div>
<div class="summary-card"><div class="summary-value">{datasets}</div><div class="summary-label">Datasets</div></div>
<div class="summary-card"><div class="summary-value">{domains}</div><div class="summary-label">Domains</div></div>
<div class="summary-card wide"><div class="summary-value small">{last_refresh}</div><div class="summary-label">Last Refresh</div></div>
</div>
"""
def dataset_tab_label(dataset: str, root_dir: str = "results") -> str:
raw_domain = lookup_dataset_domain(dataset, root_dir)
domain = display_domain(raw_domain)
short = dataset_short_name(dataset)
return f"{domain} · {short}"
def dataset_section_title(dataset: str, root_dir: str, *, ranks: bool = False) -> str:
label = dataset_tab_label(dataset, root_dir)
if ranks:
return f"**{label} · ranks (lower is better)**"
return f"**{label} · metric values (absolute, lower is better)**"
BASELINE_RANK_HISTORY_COLUMNS = [
"date",
"model",
"MSE_Rank",
"CRPS_Rank",
"MASE_Rank",
"MAE_Rank",
"RMSE_Rank",
"sMAPE_Rank",
"MSIS_Rank",
"ND_Rank",
"NRMSE_Rank",
"MAPE_Rank",
]
LIVE_RANK_HISTORY_COLUMNS = [
"date",
"model",
"MSE_Rank",
"RMSE_Rank",
"MAPE_Rank",
"CRPS_Rank",
"RTG_Rank",
]
_LIVE_RANK_METRICS = {
"MSE_Rank": ("MSE", True),
"RMSE_Rank": ("RMSE", True),
"MAPE_Rank": ("MAPE", True),
"CRPS_Rank": ("CRPS", True),
"RTG_Rank": ("RTG", False),
}
_RANK_COLS = [c for c in BASELINE_RANK_HISTORY_COLUMNS if c.endswith("_Rank")]
_LIVE_RANK_COLS = [c for c in LIVE_RANK_HISTORY_COLUMNS if c.endswith("_Rank")]
_DAILY_DISPLAY_COLS = ["date", "model"] + _RANK_COLS
_WEEKLY_DISPLAY_COLS = ["week", "model"] + _RANK_COLS
_MONTHLY_DISPLAY_COLS = ["month", "model"] + _RANK_COLS
_LIVE_DAILY_DISPLAY_COLS = ["date", "model"] + _LIVE_RANK_COLS
_LIVE_WEEKLY_DISPLAY_COLS = ["week", "model"] + _LIVE_RANK_COLS
_LIVE_MONTHLY_DISPLAY_COLS = ["month", "model"] + _LIVE_RANK_COLS
def _read_rank_history(path: Path, date_col: str) -> pd.DataFrame:
"""Read a legacy rank history CSV, coerce rank columns to float, sort by date_col."""
if not path.exists():
return pd.DataFrame(columns=[date_col, "model"] + _RANK_COLS)
try:
df = pd.read_csv(path)
except Exception:
return pd.DataFrame(columns=[date_col, "model"] + _RANK_COLS)
for col in _RANK_COLS:
if col in df.columns:
df[col] = pd.to_numeric(df[col], errors="coerce").map(format_rank_number)
sort_cols = [c for c in (date_col, "model") if c in df.columns]
if sort_cols:
df = df.sort_values(sort_cols)
present = [c for c in [date_col, "model"] + _RANK_COLS if c in df.columns]
return df[present].reset_index(drop=True)
def _live_release_metrics_path(root_dir: str) -> Path:
return Path(root_dir) / "aggregates" / "live_release_metrics.csv"
def _read_live_release_metrics(root_dir: str = "results") -> pd.DataFrame:
path = _live_release_metrics_path(root_dir)
if not path.exists():
return pd.DataFrame()
try:
df = pd.read_csv(path)
except Exception:
return pd.DataFrame()
required = {"release_id", "model"}
if not required.issubset(df.columns):
return pd.DataFrame()
time_col = "release_time" if "release_time" in df.columns else "evaluated_at"
if time_col not in df.columns:
return pd.DataFrame()
df = df.copy()
df["__timestamp"] = pd.to_datetime(df[time_col], errors="coerce", utc=True)
df = df.dropna(subset=["__timestamp", "model", "release_id"])
if df.empty:
return pd.DataFrame()
for metric_col, _ascending in _LIVE_RANK_METRICS.values():
if metric_col in df.columns:
df[metric_col] = pd.to_numeric(df[metric_col], errors="coerce")
return df
def live_rank_history_model_choices(root_dir: str = "results") -> list[str]:
df = _read_live_release_metrics(root_dir)
if df.empty or "model" not in df.columns:
return []
return sorted({str(model).strip() for model in df["model"].dropna() if str(model).strip()})
def _aggregate_live_rank_history(df: pd.DataFrame, period_col: str) -> pd.DataFrame:
rank_cols: list[str] = []
work = df.copy()
work["__models_per_release"] = work.groupby("release_id")["model"].transform("nunique")
work = work[work["__models_per_release"] >= 2]
if work.empty:
return pd.DataFrame(columns=[period_col, "model"] + _LIVE_RANK_COLS)
for rank_col, (metric_col, ascending) in _LIVE_RANK_METRICS.items():
if metric_col not in work.columns:
continue
if work[metric_col].notna().sum() == 0:
continue
work[rank_col] = work.groupby("release_id")[metric_col].rank(
method="average",
ascending=ascending,
na_option="keep",
)
rank_cols.append(rank_col)
if not rank_cols:
return pd.DataFrame(columns=[period_col, "model"] + _LIVE_RANK_COLS)
grouped = (
work.groupby([period_col, "model"], dropna=False)[rank_cols]
.mean(numeric_only=True)
.round(3)
.reset_index()
)
for col in rank_cols:
grouped[col] = grouped[col].map(format_rank_number)
present = [c for c in [period_col, "model"] + _LIVE_RANK_COLS if c in grouped.columns]
return grouped[present].sort_values([period_col, "model"]).reset_index(drop=True)
def load_live_model_rank_history(root_dir: str = "results", period: str = "date") -> pd.DataFrame:
"""Compute all-model rank history from live release metrics for date/week/month."""
df = _read_live_release_metrics(root_dir)
if df.empty:
columns = {
"date": _LIVE_DAILY_DISPLAY_COLS,
"week": _LIVE_WEEKLY_DISPLAY_COLS,
"month": _LIVE_MONTHLY_DISPLAY_COLS,
}.get(period, _LIVE_DAILY_DISPLAY_COLS)
return pd.DataFrame(columns=columns)
if period == "week":
iso = df["__timestamp"].dt.isocalendar()
df["week"] = iso["year"].astype(str) + "-W" + iso["week"].astype(str).str.zfill(2)
return _aggregate_live_rank_history(df, "week")
if period == "month":
df["month"] = df["__timestamp"].dt.strftime("%Y-%m")
return _aggregate_live_rank_history(df, "month")
df["date"] = df["__timestamp"].dt.strftime("%Y-%m-%d")
return _aggregate_live_rank_history(df, "date")
def load_baseline_rank_history(root_dir: str = "results") -> pd.DataFrame:
"""Load all-model daily rank history from live releases, with legacy fallback."""
live = load_live_model_rank_history(root_dir, "date")
if not live.empty:
return live
for name in ("baseline_rank_history_daily.csv", "baseline_rank_history.csv"):
path = Path(root_dir) / name
if path.exists():
return _read_rank_history(path, "date")
return pd.DataFrame(columns=_DAILY_DISPLAY_COLS)
def load_baseline_rank_history_weekly(root_dir: str = "results") -> pd.DataFrame:
"""Load all-model weekly rank history from live releases, with legacy fallback."""
live = load_live_model_rank_history(root_dir, "week")
if not live.empty:
return live
return _read_rank_history(Path(root_dir) / "baseline_rank_history_weekly.csv", "week")
def load_baseline_rank_history_monthly(root_dir: str = "results") -> pd.DataFrame:
"""Load all-model monthly rank history from live releases, with legacy fallback."""
live = load_live_model_rank_history(root_dir, "month")
if not live.empty:
return live
daily_path = Path(root_dir) / "baseline_rank_history_daily.csv"
if not daily_path.exists():
daily_path = Path(root_dir) / "baseline_rank_history.csv"
if not daily_path.exists():
return pd.DataFrame(columns=_MONTHLY_DISPLAY_COLS)
try:
df = pd.read_csv(daily_path)
except Exception:
return pd.DataFrame(columns=_MONTHLY_DISPLAY_COLS)
if "date" not in df.columns:
return pd.DataFrame(columns=_MONTHLY_DISPLAY_COLS)
df["date"] = pd.to_datetime(df["date"], errors="coerce")
df = df.dropna(subset=["date"])
if df.empty:
return pd.DataFrame(columns=_MONTHLY_DISPLAY_COLS)
if "model" not in df.columns:
df["model"] = ""
df["month"] = df["date"].dt.strftime("%Y-%m")
for col in _RANK_COLS:
if col in df.columns:
df[col] = pd.to_numeric(df[col], errors="coerce")
present_rank_cols = [col for col in _RANK_COLS if col in df.columns]
if present_rank_cols:
monthly = (
df.groupby(["month", "model"], dropna=False)[present_rank_cols]
.mean(numeric_only=True)
.round(3)
.reset_index()
)
else:
monthly = df.loc[:, ["month", "model"]].drop_duplicates()
for col in present_rank_cols:
monthly[col] = monthly[col].map(format_rank_number)
present = [c for c in _MONTHLY_DISPLAY_COLS if c in monthly.columns]
return monthly[present].sort_values(["month", "model"]).reset_index(drop=True)
def load_fixed_window_table(
root_dir: str = "results",
window: str = "1d",
table: str = "rank",
) -> pd.DataFrame:
"""Load one full trailing-window ranking or metric table."""
if window not in {"1d", "7d", "30d"}:
raise ValueError(f"Unsupported fixed leaderboard window: {window}")
if table not in {"rank", "overall"}:
raise ValueError(f"Unsupported fixed leaderboard table: {table}")
path = Path(root_dir) / "aggregates" / f"live_{table}_{window}.csv"
if not path.exists():
return pd.DataFrame()
try:
frame = pd.read_csv(path)
except Exception:
return pd.DataFrame()
if table == "rank":
columns = [
"Rank",
"Status",
"Model",
"Win Rate",
"Eligible Opponents",
"Shared Releases",
"Shared Datasets",
"Covered Span (days)",
]
required = {
"Rank",
"Status",
"model",
"WinRate",
"eligible_opponents",
"shared_releases",
"shared_datasets",
"covered_span_days",
}
if not required.issubset(frame.columns):
return pd.DataFrame(columns=columns)
frame = frame.loc[:, list(required)].rename(
columns={
"model": "Model",
"WinRate": "Win Rate",
"eligible_opponents": "Eligible Opponents",
"shared_releases": "Shared Releases",
"shared_datasets": "Shared Datasets",
"covered_span_days": "Covered Span (days)",
}
)
frame["Rank"] = pd.to_numeric(frame["Rank"], errors="coerce").map(format_rank_number)
frame["Win Rate"] = pd.to_numeric(frame["Win Rate"], errors="coerce").map(
lambda value: "n/a" if pd.isna(value) else f"{100.0 * float(value):.2f}%"
)
frame["Covered Span (days)"] = pd.to_numeric(
frame["Covered Span (days)"], errors="coerce"
).map(format_number)
return frame.loc[:, columns].reset_index(drop=True)
columns = [
"Model",
"MSE",
"RMSE",
"MAPE",
"CRPS",
"RTG",
"Stability",
"Improvement",
"Datasets",
"Releases",
]
required = {
"model",
"MSE",
"RMSE",
"MAPE",
"CRPS",
"RTG",
"Stability",
"Improvement",
"n_datasets",
"n_releases",
}
if not required.issubset(frame.columns):
return pd.DataFrame(columns=columns)
frame = frame.loc[:, list(required)].rename(
columns={"model": "Model", "n_datasets": "Datasets", "n_releases": "Releases"}
)
for metric in ("MSE", "RMSE", "MAPE", "CRPS", "RTG", "Stability", "Improvement"):
frame[metric] = pd.to_numeric(frame[metric], errors="coerce").map(format_number)
return frame.loc[:, columns].reset_index(drop=True)
def load_fixed_window_rank(root_dir: str = "results", window: str = "1d") -> pd.DataFrame:
"""Backward-compatible wrapper for the full fixed-window ranking."""
return load_fixed_window_table(root_dir, window, "rank")
# ---------------------------------------------------------------------------
# Forecast snapshot utilities
# ---------------------------------------------------------------------------
def load_forecast_snapshots(root_dir: str, model_slug: str) -> dict[str, dict]:
"""Load all forecast JSON files for a given model slug.
Returns a dict keyed by the dataset display name (from snapshot["dataset"]).
"""
forecasts_dir = Path(root_dir) / model_slug / "forecasts"
snapshots: dict[str, dict] = {}
if not forecasts_dir.exists():
return snapshots
for fp in sorted(forecasts_dir.glob("*.json")):
try:
snap = json.loads(fp.read_text())
snapshots[snap.get("dataset", fp.stem)] = snap
except Exception:
pass
return snapshots
def _get_plotly():
"""Lazy import plotly to avoid import errors in non-chart contexts."""
try:
import plotly.graph_objects as go
return go
except ImportError:
return None
def make_forecast_plot(snapshot: dict | None) -> object | None:
"""Build a plotly Figure showing context + actuals vs p50 forecast with p10/p90 band.
Returns None if snapshot is None or plotly is unavailable.
"""
go = _get_plotly()
if go is None or snapshot is None:
return None
ctx = snapshot.get("context", [])
actuals = snapshot.get("actuals", [])
p50 = snapshot.get("p50", [])
p10 = snapshot.get("p10", [])
p90 = snapshot.get("p90", [])
n_ctx = len(ctx)
n_fut = len(actuals)
timestamps = snapshot.get("timestamps", [])
if timestamps and len(timestamps) == n_ctx + n_fut:
ctx_x = timestamps[:n_ctx]
fut_x = timestamps[n_ctx:]
x_title = "Time"
vline_x = ctx_x[-1] if n_ctx > 0 else fut_x[0]
else:
ctx_x = list(range(n_ctx))
fut_x = list(range(n_ctx, n_ctx + n_fut))
x_title = "Time step"
vline_x = n_ctx - 1.0 if n_ctx > 0 else 0
if n_ctx > 0:
fut_x = [ctx_x[-1]] + list(fut_x)
actuals = [ctx[-1]] + list(actuals)
p50 = [ctx[-1]] + list(p50) if p50 else []
p10 = [ctx[-1]] + list(p10) if p10 else []
p90 = [ctx[-1]] + list(p90) if p90 else []
n_fut = len(actuals)
fig = go.Figure()
# Context (historical)
fig.add_trace(go.Scatter(
x=ctx_x, y=ctx,
mode="lines",
name="Context (history)",
line=dict(color="#94a3b8", width=1.5),
))
# Actuals (ground truth for forecast horizon)
fig.add_trace(go.Scatter(
x=fut_x, y=actuals,
mode="lines",
name="Actual",
line=dict(color="#3b82f6", width=2),
))
# p10-p90 confidence band
if p10 and p90 and len(p10) == n_fut and len(p90) == n_fut:
fig.add_trace(go.Scatter(
x=fut_x + fut_x[::-1],
y=p90 + p10[::-1],
fill="toself",
fillcolor="rgba(239,68,68,0.15)",
line=dict(color="rgba(255,255,255,0)"),
name="p10-p90 band",
showlegend=True,
))
# p50 forecast
if p50 and len(p50) == n_fut:
fig.add_trace(go.Scatter(
x=fut_x, y=p50,
mode="lines",
name="Forecast (p50)",
line=dict(color="#ef4444", width=2, dash="dash"),
))
dataset_label = snapshot.get("dataset", "").split("/")[0]
model_label = snapshot.get("model", "")
evaluated_at = snapshot.get("evaluated_at", "")[:10]
fig.update_layout(
title=dict(
text=f"{dataset_label}{model_label} ({evaluated_at})",
font=dict(size=14),
),
xaxis_title=x_title,
yaxis_title="Value",
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
margin=dict(l=40, r=20, t=60, b=40),
height=350,
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
font=dict(color="black"),
xaxis=dict(gridcolor="rgba(0,0,0,0.08)"),
yaxis=dict(gridcolor="rgba(0,0,0,0.08)"),
)
# Vertical line separating context from forecast
fig.add_vline(
x=vline_x,
line_dash="dot",
line_color="rgba(0,0,0,0.3)",
annotation_text="forecast start",
annotation_font_color="black",
)
return fig
def make_rank_trend_plot(history_df: pd.DataFrame, metrics: list[str] | None = None) -> object | None:
"""Build a plotly Figure showing selected rank metrics over time (date on X axis).
Args:
history_df: DataFrame from load_baseline_rank_history() with a 'date' column.
metrics: List of rank column names to plot. Defaults to MSE_Rank and CRPS_Rank.
Returns None if fewer than 1 data point or plotly unavailable.
"""
go = _get_plotly()
if go is None or history_df is None or history_df.empty:
return None
if metrics is None:
metrics = ["MSE_Rank", "CRPS_Rank", "RMSE_Rank", "MAPE_Rank"]
date_col = "date" if "date" in history_df.columns else "week"
present_metrics = [m for m in metrics if m in history_df.columns]
if not present_metrics:
return None
COLORS = ["#f97316", "#a78bfa", "#34d399", "#60a5fa", "#fb7185", "#fbbf24"]
fig = go.Figure()
for i, col in enumerate(present_metrics):
fig.add_trace(go.Scatter(
x=history_df[date_col],
y=history_df[col],
mode="lines+markers",
name=col.replace("_Rank", ""),
line=dict(color=COLORS[i % len(COLORS)], width=2),
marker=dict(size=6),
))
fig.update_layout(
title=dict(text="Selected model rank trend over time (lower is better)", font=dict(size=14)),
xaxis_title=date_col.capitalize(),
yaxis_title="Average rank",
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
margin=dict(l=40, r=20, t=60, b=40),
height=320,
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
font=dict(color="black"),
xaxis=dict(gridcolor="rgba(0,0,0,0.08)"),
yaxis=dict(gridcolor="rgba(0,0,0,0.08)"),
)
return fig
DOMAIN_COLORS = {
"Finance": "#f97316",
"Climate": "#60a5fa",
"Ocean": "#34d399",
"Hydrology": "#818cf8",
"Transportation": "#14b8a6",
"Hazards": "#fb7185",
"Web": "#a78bfa",
}
def _today_start_utc8() -> datetime:
"""Return midnight today in UTC+8 as an aware datetime."""
utc8 = timezone(timedelta(hours=8))
return datetime.now(utc8).replace(hour=0, minute=0, second=0, microsecond=0)
def get_dataset_last_eval_time(ds_base: str, root_dir: str = "results") -> datetime | None:
latest_dt: datetime | None = None
history_path = Path(root_dir) / "eval_history.jsonl"
if history_path.exists():
try:
with history_path.open(encoding="utf-8") as handle:
for line in handle:
if not line.strip():
continue
entry = json.loads(line)
if _dataset_key(entry.get("dataset", "")) != _dataset_key(ds_base):
continue
dt = _parse_timestamp(entry.get("evaluated_at"))
if dt is not None and (latest_dt is None or dt > latest_dt):
latest_dt = dt
except Exception:
pass
for model_dir in Path(root_dir).iterdir():
if not model_dir.is_dir():
continue
meta_path = model_dir / "online_meta.json"
if not meta_path.exists():
continue
try:
meta = json.loads(meta_path.read_text())
except Exception:
continue
matched = False
for item in meta.get("datasets", []):
raw_name = str(item.get("dataset", ""))
if _dataset_key(raw_name) == _dataset_key(ds_base) or ds_base.lower() in raw_name.lower():
matched = True
break
if not matched:
continue
dt = _parse_timestamp(meta.get("evaluated_at"))
if dt is not None and (latest_dt is None or dt > latest_dt):
latest_dt = dt
return latest_dt
def make_domain_pie_chart(root_dir: str = "results") -> object | None:
"""Build a plotly Pie chart showing evaluated timestamp count per domain today.
Reads eval_history.jsonl to count total evaluated timestamps since UTC+8 00:00 today,
grouped by domain.
"""
go = _get_plotly()
if go is None:
return None
# Domain mapping from dataset_properties.csv
props_path = Path(root_dir) / "dataset_properties.csv"
if not props_path.exists():
return None
try:
props = pd.read_csv(props_path)
except Exception:
return None
domain_map: dict[str, str] = {}
for _, row in props.iterrows():
domain_map[str(row["dataset"]).lower()] = display_domain(str(row["domain"]))
def get_domain(ds_name: str) -> str:
ds_lower = ds_name.lower()
if ds_lower not in domain_map:
domain_map[ds_lower] = display_domain(lookup_dataset_domain(ds_name, root_dir))
return domain_map[ds_lower]
today_start = _today_start_utc8()
utc8 = today_start.tzinfo
# Read eval_history.jsonl (Cumulative)
history_path = Path(root_dir) / "eval_history.jsonl"
timestamp_counts: dict[str, int] = {} # domain → total timestamps
domain_datasets: dict[str, set[str]] = {} # domain → set of dataset short names
if history_path.exists():
try:
with open(history_path) as f:
for line in f:
if not line.strip():
continue
entry = json.loads(line)
ds_base = entry.get("dataset", "")
pred_len = entry.get("prediction_length", 0)
domain = get_domain(ds_base)
timestamp_counts[domain] = timestamp_counts.get(domain, 0) + pred_len
domain_datasets.setdefault(domain, set()).add(dataset_short_name(ds_base))
except Exception:
pass
# Fallback to one count per current dataset if history is unavailable.
if not timestamp_counts:
for ds_base, domain in domain_map.items():
timestamp_counts[domain] = timestamp_counts.get(domain, 0) + 1
domain_datasets.setdefault(domain, set()).add(dataset_short_name(ds_base))
if not timestamp_counts:
return None
labels = list(timestamp_counts.keys())
values = list(timestamp_counts.values())
hover = [", ".join(sorted(domain_datasets.get(d, []))) for d in labels]
colors = [DOMAIN_COLORS.get(label, "#94a3b8") for label in labels]
fig = go.Figure(data=[go.Pie(
labels=labels,
values=values,
hovertext=hover,
hoverinfo="label+percent+text",
textinfo="label+value+percent",
textposition="inside",
textfont=dict(size=14, color="white"),
insidetextorientation="radial",
marker=dict(colors=colors),
hole=0.25,
)])
fig.update_layout(
title=dict(
text="Evaluated timestamps by domain",
font=dict(size=16, weight="bold"),
),
margin=dict(l=20, r=20, t=60, b=20),
height=500,
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
font=dict(color="black"),
showlegend=True,
legend=dict(
orientation="h",
yanchor="bottom",
y=-0.1,
xanchor="center",
x=0.5
)
)
return fig
# ---------------------------------------------------------------------------
# Per-domain status helpers
# ---------------------------------------------------------------------------
def load_eval_intervals(root_dir: str = "results") -> dict[str, int]:
"""Load per-dataset eval intervals from dataset_eval_intervals.json."""
path = Path(root_dir) / "dataset_eval_intervals.json"
if not path.exists():
return {}
try:
return {str(k): int(v) for k, v in json.loads(path.read_text()).items()}
except Exception:
return {}
def _format_interval(seconds: int) -> str:
if seconds >= 86400:
d = seconds // 86400
return f"{d}d"
if seconds >= 3600:
h = seconds // 3600
return f"{h}h"
if seconds >= 60:
m = seconds // 60
return f"{m}min"
return f"{seconds}s"
def build_domain_status_md(
domain: str,
datasets: list[str],
root_dir: str = "results",
) -> str:
"""Build a compact markdown status line for a domain tab.
Shows data source, last eval time, last evaluated time for this domain,
and per-dataset refresh intervals.
"""
utc8 = timezone(timedelta(hours=8))
# 1. Load data source name from online_status.json
status_path = Path(root_dir) / "online_status.json"
data_source = "TS-Bench"
if status_path.exists():
try:
status = json.loads(status_path.read_text())
data_source = status.get("data_source", "TS-Bench")
except Exception:
pass
# 2. Last evaluated time for this domain (max of all datasets in the domain)
domain_latest_dt: datetime | None = None
for ds in datasets:
ds_base = ds.split("/")[0].strip()
eval_dt = get_dataset_last_eval_time(ds_base, root_dir)
if eval_dt:
if domain_latest_dt is None or eval_dt > domain_latest_dt:
domain_latest_dt = eval_dt
last_evaluated = "n/a"
if domain_latest_dt:
last_evaluated = domain_latest_dt.astimezone(utc8).strftime("%Y-%m-%d %H:%M:%S") + " (UTC+8)"
# 3. Auto-refresh per-dataset
intervals = load_eval_intervals(root_dir)
ds_parts: list[str] = []
for ds in datasets:
short = dataset_short_name(ds)
ds_base = ds.split("/")[0].strip()
interval = intervals.get(ds_base)
if interval:
ds_parts.append(f"{short} every {_format_interval(interval)}")
else:
ds_parts.append(short)
parts = [
f"**Data**: {data_source}",
f"**Last eval**: {last_evaluated}",
]
if ds_parts:
parts.append("**Refresh**: " + " · ".join(ds_parts))
return " | ".join(parts)