zqiao11 commited on
Commit
e580cf0
·
1 Parent(s): 7a0d3ad

perf: add LRU caching layers and Gradio queue for concurrency

Browse files
Files changed (3) hide show
  1. app.py +2 -0
  2. src/leaderboard.py +179 -134
  3. src/tab.py +51 -139
app.py CHANGED
@@ -65,5 +65,7 @@ with gr.Blocks(css=custom_css, head=custom_head) as demo:
65
  # init_archive_tab(demo)
66
 
67
 
 
 
68
  if __name__ == "__main__":
69
  demo.launch()
 
65
  # init_archive_tab(demo)
66
 
67
 
68
+ demo.queue(default_concurrency_limit=5)
69
+
70
  if __name__ == "__main__":
71
  demo.launch()
src/leaderboard.py CHANGED
@@ -10,6 +10,7 @@ from src.about import DATASETS_DF, OVERALL_TABLE_COLUMNS, ALL_MODELS, RESULTS_RO
10
  from src.hf_config import get_datasets_root, get_config_root
11
  from src.utils import normalize_by_seasonal_naive
12
  import ast
 
13
  from timebench.evaluation.data import Dataset, get_dataset_settings, load_dataset_config
14
  from pathlib import Path
15
 
@@ -83,6 +84,113 @@ def find_dataset_term_path(results_root, model_name, display_name):
83
 
84
  return None
85
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  def load_test_windows(display_name, horizon, model_name="moirai_small", series=None, variate=None, window_id=None, parse_series=False):
87
  """
88
  Load test window results from TIME NPZ files.
@@ -107,65 +215,30 @@ def load_test_windows(display_name, horizon, model_name="moirai_small", series=N
107
  if dataset_term is None:
108
  return None
109
 
110
- horizon_dir = os.path.join(results_root, model_name, dataset_term, horizon)
111
- metrics_path = os.path.join(horizon_dir, "metrics.npz")
112
-
113
- # Load data
114
- metrics = np.load(metrics_path)
115
 
116
- # Get array shapes
117
- mase_arr = metrics["MASE"] # (num_series, num_windows, num_variates)
118
  num_series, num_windows, num_variates = mase_arr.shape
119
 
120
- # Load Dataset to get actual names
 
121
  series_names = None
122
  variate_names = None
123
- try:
124
- # Use HF config to get dataset root (handles both local and HF Hub)
125
- hf_dataset_root = str(get_datasets_root())
126
-
127
- if os.path.exists(hf_dataset_root):
128
- # Use HF config to get config root
129
- config_root = get_config_root()
130
- config_path = config_root / "datasets.yaml"
131
-
132
- config = load_dataset_config(config_path) if config_path.exists() else {}
133
- settings = get_dataset_settings(dataset_term, horizon, config)
134
-
135
- prediction_length = settings.get("prediction_length")
136
- test_length = settings.get("test_length")
137
-
138
- # Load dataset with storage_path parameter
139
- dataset_obj = Dataset(
140
- name=dataset_term,
141
- term=horizon,
142
- prediction_length=prediction_length,
143
- test_length=test_length,
144
- storage_path=hf_dataset_root,
145
- )
146
-
147
- # Get series names (item_id)
148
- if "item_id" in dataset_obj.hf_dataset.column_names:
149
- series_names = dataset_obj.hf_dataset["item_id"]
150
- else:
151
- series_names = [dataset_obj.hf_dataset[i].get("item_id", f"item_{i}")
152
- for i in range(len(dataset_obj.hf_dataset))]
153
-
154
- # Get variate names
155
- variate_names = dataset_obj.get_variate_names()
156
- if variate_names is None:
157
- # Univariate mode: variate names are same as series names
158
- variate_names = series_names
159
- except Exception as e:
160
- print(f"Error loading Dataset for names: {e}")
161
-
162
- # Create name to index mappings
163
  series_name_to_idx = {}
164
  variate_name_to_idx = {}
165
- if series_names is not None:
166
- series_name_to_idx = {name: idx for idx, name in enumerate(series_names)}
167
- if variate_names is not None:
168
- variate_name_to_idx = {name: idx for idx, name in enumerate(variate_names)}
 
 
 
 
 
 
169
 
170
  # Convert series and variate names to indices if they are names
171
  series_idx_filter = None
@@ -219,9 +292,9 @@ def load_test_windows(display_name, horizon, model_name="moirai_small", series=N
219
  "variate_name": variate_name,
220
  "window_id": window_idx,
221
  "MASE": float(mase_arr[series_idx, window_idx, variate_idx]),
222
- "CRPS": float(metrics["CRPS"][series_idx, window_idx, variate_idx]),
223
- "MAE": float(metrics["MAE"][series_idx, window_idx, variate_idx]),
224
- "MSE": float(metrics["MSE"][series_idx, window_idx, variate_idx]),
225
  "model": model_name,
226
  "series_idx": series_idx, # Keep for reference
227
  "variate_idx": variate_idx, # Keep for reference
@@ -416,60 +489,22 @@ def get_dataset_leaderboard(
416
  if df_filtered.empty:
417
  return f"No results found for dataset '{display_name}' with horizons {horizons}.", pd.DataFrame(columns=["model"])
418
 
419
- # Get dataset information (series count, variate count, freq)
420
  dataset_info_msg = ""
421
  try:
422
- # Parse dataset_id to get freq
423
- if "/" in dataset_id:
424
- _, freq = dataset_id.split("/", 1)
425
- else:
426
- freq = "unknown"
427
-
428
- # Load Dataset to get series and variate counts
429
- hf_dataset_root = str(get_datasets_root())
430
- config_root = get_config_root()
431
- config_path = config_root / "datasets.yaml"
432
-
433
- if os.path.exists(hf_dataset_root) and config_path.exists():
434
- config = load_dataset_config(config_path)
435
- settings = get_dataset_settings(dataset_id, horizons[0] if horizons else "short", config)
436
-
437
- dataset_obj = Dataset(
438
- name=dataset_id,
439
- term=horizons[0] if horizons else "short",
440
- prediction_length=settings.get("prediction_length"),
441
- test_length=settings.get("test_length"),
442
- storage_path=hf_dataset_root,
443
- )
444
-
445
- # Get series count
446
- if "item_id" in dataset_obj.hf_dataset.column_names:
447
- series_names = dataset_obj.hf_dataset["item_id"]
448
- # Convert to list if it's an array/Series to avoid ambiguity in boolean check
449
- if isinstance(series_names, (np.ndarray, pd.Series)):
450
- series_names = list(series_names)
451
- elif not isinstance(series_names, list):
452
- series_names = list(series_names) if hasattr(series_names, '__iter__') else [series_names]
453
- # Use len() check instead of boolean check to avoid ambiguity
454
- if len(series_names) > 0:
455
- num_series = len(set(series_names))
456
- else:
457
- num_series = len(dataset_obj.hf_dataset)
458
- else:
459
- num_series = len(dataset_obj.hf_dataset)
460
-
461
- # Get variate count
462
- variate_names = dataset_obj.get_variate_names()
463
- if variate_names is not None:
464
- num_variates = len(variate_names)
465
- else:
466
- # UTS: each series is one variate
467
- num_variates = 1
468
-
469
  dataset_info_msg = f"📊 Dataset Info: {num_series} series, {num_variates} variates, freq={freq}"
 
 
 
 
470
  except Exception as e:
471
  print(f"Error getting dataset info: {e}")
472
- # If we can't get info, try to extract freq from dataset_id
473
  if "/" in dataset_id:
474
  _, freq = dataset_id.split("/", 1)
475
  dataset_info_msg = f"📊 Dataset Info: freq={freq}"
@@ -577,6 +612,16 @@ def get_dataset_leaderboard(
577
  def get_dataset_multilevel_leaderboard(display_name, series, variate, horizons, metric: str = "MASE"):
578
  """
579
  Get leaderboard based on dataset, series, and variate selections.
 
 
 
 
 
 
 
 
 
 
580
 
581
  Logic:
582
  0. If only dataset selected (series="---", variate="---"): return dataset-level results
@@ -587,12 +632,14 @@ def get_dataset_multilevel_leaderboard(display_name, series, variate, horizons,
587
  display_name: Dataset display name from UI (will be converted to dataset_id)
588
  series: Series name or "---" if not selected
589
  variate: Variate name or "---" if not selected
590
- horizons: List of horizons to include
591
  metric: Metric for sorting
592
 
593
  Returns:
594
  tuple: (message, DataFrame)
595
  """
 
 
596
  # Case 0: Only dataset selected - return both original and normalized metrics
597
  if (series is None or series == "---" or series == "") and (variate is None or variate == "---" or variate == ""):
598
  return get_dataset_leaderboard(display_name, horizons, metric)
@@ -606,28 +653,13 @@ def get_dataset_multilevel_leaderboard(display_name, series, variate, horizons,
606
  if dataset_term is None:
607
  return f"Dataset '{display_name}' not found.", pd.DataFrame(columns=["model", "MASE", "CRPS", "MAE", "MSE"])
608
 
609
- # Check if dataset is UTS or MTS
610
  is_uts = False
611
  try:
612
- hf_dataset_root = str(get_datasets_root())
613
-
614
- if os.path.exists(hf_dataset_root):
615
- config_root = get_config_root()
616
- config_path = config_root / "datasets.yaml"
617
-
618
- config = load_dataset_config(config_path) if config_path.exists() else {}
619
- settings = get_dataset_settings(dataset_term, horizons[0] if horizons else "short", config)
620
-
621
- dataset_obj = Dataset(
622
- name=dataset_term,
623
- term=horizons[0] if horizons else "short",
624
- prediction_length=settings.get("prediction_length"),
625
- test_length=settings.get("test_length"),
626
- storage_path=hf_dataset_root,
627
- )
628
-
629
- variate_names = dataset_obj.get_variate_names()
630
- is_uts = (variate_names is None)
631
  except Exception as e:
632
  print(f"Error checking UTS/MTS: {e}")
633
 
@@ -678,9 +710,11 @@ def get_dataset_multilevel_leaderboard(display_name, series, variate, horizons,
678
 
679
 
680
 
 
681
  def get_window_leaderboard(display_name, series, variate, window_id, horizon, metric: str = "MASE"):
682
  """
683
  Get leaderboard for a specific test window.
 
684
 
685
  Args:
686
  display_name: Dataset display name from UI (will be converted to dataset_id)
@@ -723,24 +757,35 @@ def get_pattern_leaderboard(
723
  ) -> tuple[str, pd.DataFrame]:
724
  """
725
  Filter variates by selected patterns and compute average metrics per model.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
726
 
727
  Uses FEATURES_BOOL_DF (binarized features) to filter variates,
728
  then joins with VARIATES_DF to get metrics.
729
 
730
  Args:
731
- pattern_filters: Dict mapping pattern names to required values.
732
- - {feature_name: required_value} where required_value is 0 or 1.
733
- - Features with "Any" selection are not included in the dict.
734
- - Example: {"T_strength": 1, "S_strength": 0} means:
735
- - T_strength must be 1 (has the feature)
736
- - S_strength must be 0 (does not have the feature)
737
- selected_horizons: List of horizons to include (e.g., ["short", "medium"])
738
 
739
  Returns:
740
  tuple: (message, leaderboard_df)
741
- - message: Status message with matching count
742
- - leaderboard_df: DataFrame with model metrics, sorted by MASE
743
  """
 
 
 
 
744
  from src.about import VARIATES_DF, FEATURES_DF, FEATURES_BOOL_DF, PATTERN_MAP
745
 
746
  # Check if data is available
 
10
  from src.hf_config import get_datasets_root, get_config_root
11
  from src.utils import normalize_by_seasonal_naive
12
  import ast
13
+ from functools import lru_cache
14
  from timebench.evaluation.data import Dataset, get_dataset_settings, load_dataset_config
15
  from pathlib import Path
16
 
 
84
 
85
  return None
86
 
87
+
88
+ # =============================================================================
89
+ # Cached Helpers (Layer 1 & Layer 2)
90
+ # =============================================================================
91
+
92
+ @lru_cache(maxsize=20)
93
+ def _get_dataset_metadata(dataset_term, horizon):
94
+ """
95
+ Load and cache Dataset metadata (series names, variate names, etc.).
96
+
97
+ This is the **Layer 1 cache** — the single most impactful optimization.
98
+ Creating a Dataset object loads an HF dataset from disk, which takes
99
+ hundreds of milliseconds. By caching the result, subsequent calls for
100
+ the same (dataset_term, horizon) return instantly.
101
+
102
+ Args:
103
+ dataset_term: Dataset term path, e.g. "Traffic/15T"
104
+ horizon: Horizon name ("short", "medium", "long")
105
+
106
+ Returns:
107
+ dict with keys: dataset_obj, series_names, variate_names, is_uts,
108
+ series_name_to_idx, variate_name_to_idx, num_windows, freq
109
+ or None on failure.
110
+ """
111
+ try:
112
+ hf_dataset_root = str(get_datasets_root())
113
+ config_root = get_config_root()
114
+ config_path = config_root / "datasets.yaml"
115
+
116
+ config = load_dataset_config(config_path) if config_path.exists() else {}
117
+ settings = get_dataset_settings(dataset_term, horizon, config)
118
+
119
+ dataset_obj = Dataset(
120
+ name=dataset_term,
121
+ term=horizon,
122
+ prediction_length=settings.get("prediction_length"),
123
+ test_length=settings.get("test_length"),
124
+ storage_path=hf_dataset_root,
125
+ )
126
+
127
+ # Get series names
128
+ if "item_id" in dataset_obj.hf_dataset.column_names:
129
+ series_names = list(dataset_obj.hf_dataset["item_id"])
130
+ else:
131
+ series_names = [dataset_obj.hf_dataset[i].get("item_id", f"item_{i}")
132
+ for i in range(len(dataset_obj.hf_dataset))]
133
+
134
+ # Get variate names
135
+ variate_names = dataset_obj.get_variate_names()
136
+ is_uts = (variate_names is None)
137
+ if variate_names is not None:
138
+ variate_names = list(variate_names)
139
+
140
+ # Build name-to-index mappings
141
+ series_name_to_idx = {name: idx for idx, name in enumerate(series_names)}
142
+ variate_name_to_idx = {}
143
+ if variate_names is not None:
144
+ variate_name_to_idx = {name: idx for idx, name in enumerate(variate_names)}
145
+
146
+ # Window count & frequency
147
+ num_windows = dataset_obj.windows
148
+ freq = dataset_obj.freq
149
+
150
+ return {
151
+ "dataset_obj": dataset_obj,
152
+ "series_names": series_names,
153
+ "variate_names": variate_names, # None for UTS
154
+ "is_uts": is_uts,
155
+ "series_name_to_idx": series_name_to_idx,
156
+ "variate_name_to_idx": variate_name_to_idx, # empty dict for UTS
157
+ "num_windows": num_windows,
158
+ "freq": freq,
159
+ }
160
+ except Exception as e:
161
+ print(f"[_get_dataset_metadata] Error for {dataset_term}/{horizon}: {e}")
162
+ return None
163
+
164
+
165
+ @lru_cache(maxsize=100)
166
+ def _load_metrics_cached(model_name, dataset_term, horizon):
167
+ """
168
+ Load and cache metrics.npz arrays for a specific (model, dataset, horizon).
169
+
170
+ This is the **Layer 2 cache**. Each call to np.load opens a file and
171
+ reads arrays from disk. In get_window_leaderboard, load_test_windows
172
+ is called once per model (~15 times), each loading the same-format
173
+ file. Caching avoids repeated disk I/O.
174
+
175
+ Args:
176
+ model_name: Model name (e.g. "moirai_small")
177
+ dataset_term: Dataset term path (e.g. "Traffic/15T")
178
+ horizon: Horizon name ("short", "medium", "long")
179
+
180
+ Returns:
181
+ dict mapping metric names to numpy arrays, or None if file missing.
182
+ """
183
+ results_root = str(RESULTS_ROOT)
184
+ metrics_path = os.path.join(results_root, model_name, dataset_term, horizon, "metrics.npz")
185
+ if not os.path.exists(metrics_path):
186
+ return None
187
+ npz = np.load(metrics_path)
188
+ # Materialise into a plain dict so the NpzFile handle can be closed
189
+ result = {k: npz[k] for k in npz.files}
190
+ npz.close()
191
+ return result
192
+
193
+
194
  def load_test_windows(display_name, horizon, model_name="moirai_small", series=None, variate=None, window_id=None, parse_series=False):
195
  """
196
  Load test window results from TIME NPZ files.
 
215
  if dataset_term is None:
216
  return None
217
 
218
+ # --- Layer 2 cache: metrics npz ---
219
+ metrics_dict = _load_metrics_cached(model_name, dataset_term, horizon)
220
+ if metrics_dict is None:
221
+ return None
 
222
 
223
+ mase_arr = metrics_dict["MASE"] # (num_series, num_windows, num_variates)
 
224
  num_series, num_windows, num_variates = mase_arr.shape
225
 
226
+ # --- Layer 1 cache: dataset metadata ---
227
+ metadata = _get_dataset_metadata(dataset_term, horizon)
228
  series_names = None
229
  variate_names = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  series_name_to_idx = {}
231
  variate_name_to_idx = {}
232
+ if metadata is not None:
233
+ series_names = metadata["series_names"]
234
+ variate_names = metadata["variate_names"]
235
+ series_name_to_idx = metadata["series_name_to_idx"]
236
+ if metadata["is_uts"]:
237
+ # UTS mode: variate names are same as series names
238
+ variate_names = series_names
239
+ variate_name_to_idx = {name: idx for idx, name in enumerate(variate_names)}
240
+ else:
241
+ variate_name_to_idx = metadata["variate_name_to_idx"]
242
 
243
  # Convert series and variate names to indices if they are names
244
  series_idx_filter = None
 
292
  "variate_name": variate_name,
293
  "window_id": window_idx,
294
  "MASE": float(mase_arr[series_idx, window_idx, variate_idx]),
295
+ "CRPS": float(metrics_dict["CRPS"][series_idx, window_idx, variate_idx]),
296
+ "MAE": float(metrics_dict["MAE"][series_idx, window_idx, variate_idx]),
297
+ "MSE": float(metrics_dict["MSE"][series_idx, window_idx, variate_idx]),
298
  "model": model_name,
299
  "series_idx": series_idx, # Keep for reference
300
  "variate_idx": variate_idx, # Keep for reference
 
489
  if df_filtered.empty:
490
  return f"No results found for dataset '{display_name}' with horizons {horizons}.", pd.DataFrame(columns=["model"])
491
 
492
+ # Get dataset information (series count, variate count, freq) via cached metadata
493
  dataset_info_msg = ""
494
  try:
495
+ horizon_for_meta = horizons[0] if horizons else "short"
496
+ metadata = _get_dataset_metadata(dataset_id, horizon_for_meta)
497
+ if metadata is not None:
498
+ num_series = len(set(metadata["series_names"]))
499
+ num_variates = len(metadata["variate_names"]) if metadata["variate_names"] is not None else 1
500
+ freq = metadata["freq"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
501
  dataset_info_msg = f"📊 Dataset Info: {num_series} series, {num_variates} variates, freq={freq}"
502
+ else:
503
+ if "/" in dataset_id:
504
+ _, freq = dataset_id.split("/", 1)
505
+ dataset_info_msg = f"📊 Dataset Info: freq={freq}"
506
  except Exception as e:
507
  print(f"Error getting dataset info: {e}")
 
508
  if "/" in dataset_id:
509
  _, freq = dataset_id.split("/", 1)
510
  dataset_info_msg = f"📊 Dataset Info: freq={freq}"
 
612
  def get_dataset_multilevel_leaderboard(display_name, series, variate, horizons, metric: str = "MASE"):
613
  """
614
  Get leaderboard based on dataset, series, and variate selections.
615
+ Thin wrapper that converts mutable args to hashable types for caching.
616
+ """
617
+ horizons_key = tuple(sorted(horizons)) if horizons else ()
618
+ return _get_dataset_multilevel_leaderboard_cached(display_name, series, variate, horizons_key, metric)
619
+
620
+
621
+ @lru_cache(maxsize=30)
622
+ def _get_dataset_multilevel_leaderboard_cached(display_name, series, variate, horizons_key, metric: str = "MASE"):
623
+ """
624
+ Cached implementation of get_dataset_multilevel_leaderboard.
625
 
626
  Logic:
627
  0. If only dataset selected (series="---", variate="---"): return dataset-level results
 
632
  display_name: Dataset display name from UI (will be converted to dataset_id)
633
  series: Series name or "---" if not selected
634
  variate: Variate name or "---" if not selected
635
+ horizons_key: Tuple of horizons (hashable)
636
  metric: Metric for sorting
637
 
638
  Returns:
639
  tuple: (message, DataFrame)
640
  """
641
+ horizons = list(horizons_key)
642
+
643
  # Case 0: Only dataset selected - return both original and normalized metrics
644
  if (series is None or series == "---" or series == "") and (variate is None or variate == "---" or variate == ""):
645
  return get_dataset_leaderboard(display_name, horizons, metric)
 
653
  if dataset_term is None:
654
  return f"Dataset '{display_name}' not found.", pd.DataFrame(columns=["model", "MASE", "CRPS", "MAE", "MSE"])
655
 
656
+ # Check if dataset is UTS or MTS using cached metadata
657
  is_uts = False
658
  try:
659
+ horizon_for_meta = horizons[0] if horizons else "short"
660
+ metadata = _get_dataset_metadata(dataset_term, horizon_for_meta)
661
+ if metadata is not None:
662
+ is_uts = metadata["is_uts"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
663
  except Exception as e:
664
  print(f"Error checking UTS/MTS: {e}")
665
 
 
710
 
711
 
712
 
713
+ @lru_cache(maxsize=30)
714
  def get_window_leaderboard(display_name, series, variate, window_id, horizon, metric: str = "MASE"):
715
  """
716
  Get leaderboard for a specific test window.
717
+ Cached because all arguments are hashable strings.
718
 
719
  Args:
720
  display_name: Dataset display name from UI (will be converted to dataset_id)
 
757
  ) -> tuple[str, pd.DataFrame]:
758
  """
759
  Filter variates by selected patterns and compute average metrics per model.
760
+ Thin wrapper that converts mutable args to hashable types for caching.
761
+ """
762
+ filters_key = frozenset(pattern_filters.items()) if pattern_filters else frozenset()
763
+ horizons_key = tuple(sorted(selected_horizons)) if selected_horizons else ()
764
+ return _get_pattern_leaderboard_cached(filters_key, horizons_key)
765
+
766
+
767
+ @lru_cache(maxsize=30)
768
+ def _get_pattern_leaderboard_cached(
769
+ filters_key: frozenset,
770
+ horizons_key: tuple,
771
+ ) -> tuple[str, pd.DataFrame]:
772
+ """
773
+ Cached implementation of get_pattern_leaderboard.
774
 
775
  Uses FEATURES_BOOL_DF (binarized features) to filter variates,
776
  then joins with VARIATES_DF to get metrics.
777
 
778
  Args:
779
+ filters_key: frozenset of (pattern_name, required_value) pairs
780
+ horizons_key: tuple of horizon strings
 
 
 
 
 
781
 
782
  Returns:
783
  tuple: (message, leaderboard_df)
 
 
784
  """
785
+ # Convert back from hashable types
786
+ pattern_filters = dict(filters_key)
787
+ selected_horizons = list(horizons_key)
788
+
789
  from src.about import VARIATES_DF, FEATURES_DF, FEATURES_BOOL_DF, PATTERN_MAP
790
 
791
  # Check if data is available
src/tab.py CHANGED
@@ -31,9 +31,10 @@ import json
31
  import gradio as gr
32
  from src.about import DATASET_CHOICES, ALL_MODELS, RESULTS_ROOT, FEATURES_DF, FEATURES_BOOL_DF, PATTERN_MAP
33
  from src.leaderboard import (get_overall_leaderboard, get_dataset_multilevel_leaderboard,
34
- get_window_leaderboard, get_pattern_leaderboard, resolve_dataset_id)
 
35
  from src.about import DATASETS_DF, ALL_HORIZONS
36
- from src.hf_config import get_datasets_root, get_config_root
37
  import numpy as np
38
  import pandas as pd
39
  from pathlib import Path
@@ -43,7 +44,7 @@ matplotlib.use('Agg') # Use non-interactive backend for Gradio
43
  import yaml
44
  import tempfile
45
 
46
- from timebench.evaluation.data import Dataset, get_dataset_settings, load_dataset_config
47
  from src.leaderboard import find_dataset_term_path
48
 
49
 
@@ -86,42 +87,17 @@ def update_series_and_variate(display_name):
86
  gr.Dropdown(choices=["---"], value="---", label="Select Variate", interactive=True),
87
  )
88
 
89
- # Load Dataset to get actual series and variate names
90
- # Use HF config to get dataset root (handles both local and HF Hub)
91
- hf_dataset_root = str(get_datasets_root())
92
-
93
- # Use HF config to get config root
94
- config_root = get_config_root()
95
- config_path = config_root / "datasets.yaml"
96
-
97
- # horizon doesn't affect series and variate values, so use "short" directly
98
- config = load_dataset_config(config_path)
99
- settings = get_dataset_settings(dataset_term, "short", config)
100
-
101
- prediction_length = settings.get("prediction_length")
102
- test_length = settings.get("test_length")
103
-
104
- dataset_obj = Dataset(
105
- name=dataset_term,
106
- term="short",
107
- prediction_length=prediction_length,
108
- test_length=test_length,
109
- storage_path=hf_dataset_root, # Pass storage path directly
110
- )
111
-
112
- # Get series names
113
- if "item_id" in dataset_obj.hf_dataset.column_names:
114
- series_names = dataset_obj.hf_dataset["item_id"]
115
- else:
116
- series_names = [dataset_obj.hf_dataset[i].get("item_id", f"item_{i}")
117
- for i in range(len(dataset_obj.hf_dataset))]
118
-
119
- series_list = ["---"] + [str(name) for name in series_names]
120
 
121
- # Get variate names
122
- variate_names = dataset_obj.get_variate_names()
123
 
124
- if variate_names is None:
125
  # UTS mode: variate dropdown should be disabled
126
  return (
127
  gr.Dropdown(choices=series_list, value="---", label="Select Series", interactive=True),
@@ -129,7 +105,7 @@ def update_series_and_variate(display_name):
129
  )
130
  else:
131
  # MTS mode: both dropdowns are enabled
132
- variates_list = ["---"] + [str(name) for name in variate_names]
133
  return (
134
  gr.Dropdown(choices=series_list, value="---", label="Select Series", interactive=True),
135
  gr.Dropdown(choices=variates_list, value="---", label="Select Variate", interactive=True),
@@ -234,52 +210,20 @@ def update_series_variate_and_window(display_name, horizon):
234
  gr.Dropdown(choices=[], value=None, label="Select Testing Window", interactive=False),
235
  )
236
 
237
- # Parse dataset_name and freq from dataset_term (format: "dataset_name/freq")
238
- dataset_name, freq = dataset_term.split("/", 1)
239
-
240
- # Load Dataset to get actual series and variate names
241
- # Use HF config to get dataset root (handles both local and HF Hub)
242
- hf_dataset_root = str(get_datasets_root())
243
-
244
- # Use HF config to get config root
245
- config_root = get_config_root()
246
- config_path = config_root / "datasets.yaml"
247
-
248
- config = load_dataset_config(config_path) if config_path.exists() else {}
249
- settings = get_dataset_settings(dataset_term, horizon, config)
250
-
251
- prediction_length = settings.get("prediction_length")
252
- test_length = settings.get("test_length")
253
-
254
- # Load dataset
255
- dataset_obj = Dataset(
256
- name=dataset_term,
257
- term=horizon,
258
- prediction_length=prediction_length,
259
- test_length=test_length,
260
- storage_path=hf_dataset_root, # Pass storage path directly
261
- )
262
-
263
- # Get series names (item_id) from hf_dataset
264
- if "item_id" in dataset_obj.hf_dataset.column_names:
265
- series_names = dataset_obj.hf_dataset["item_id"]
266
- else:
267
- # Fallback: get from iterating
268
- series_names = [dataset_obj.hf_dataset[i].get("item_id", f"item_{i}")
269
- for i in range(len(dataset_obj.hf_dataset))]
270
-
271
- # Get variate names
272
- variate_names = dataset_obj.get_variate_names()
273
-
274
- # Get window count
275
- num_windows = dataset_obj.windows
276
- windows = [str(i) for i in range(num_windows)]
277
 
278
- # Convert to lists and maintain order (no sorting)
279
- series_list = [str(name) for name in series_names]
280
 
281
  # Handle UTS (Univariate Time Series) vs MTS (Multivariate Time Series)
282
- if variate_names is None:
283
  # UTS mode: each series is a single variate, so variate is always 0
284
  return (
285
  gr.Dropdown(choices=series_list, value=series_list[0], label="Select Series", interactive=True),
@@ -288,7 +232,7 @@ def update_series_variate_and_window(display_name, horizon):
288
  )
289
  else:
290
  # MTS mode: multiple variates per series
291
- variates_list = [str(name) for name in variate_names]
292
  return (
293
  gr.Dropdown(choices=series_list, value=series_list[0], label="Select Series", interactive=True),
294
  gr.Dropdown(choices=variates_list, value=variates_list[0], label="Select Variate", interactive=True),
@@ -358,51 +302,25 @@ def plot_window_series(display_name, series, variate, window_id, horizon, select
358
  print(f"📊 Applying inverse scale factor: {prediction_scale_factor}")
359
  predictions_quantiles = predictions_quantiles.astype(np.float32) * prediction_scale_factor
360
 
361
- # Convert series and variate names to indices
362
- series_idx = None
363
- variate_idx = None
364
- dataset_obj = None
365
-
366
- # Load Dataset to get name-to-index mappings and full time series
367
- # Use HF config to get dataset root (handles both local and HF Hub)
368
- hf_dataset_root = str(get_datasets_root())
369
- print(f"📁 hf_dataset_root: {hf_dataset_root}, exists: {os.path.exists(hf_dataset_root)}")
370
-
371
- # Use HF config to get config root
372
- config_root = get_config_root()
373
- config_path_yaml = config_root / "datasets.yaml"
374
- print(f"📁 config_path_yaml: {config_path_yaml}, exists: {config_path_yaml.exists()}")
375
-
376
- config = load_dataset_config(config_path_yaml) if config_path_yaml.exists() else {}
377
- settings = get_dataset_settings(dataset_term, horizon, config)
378
- print(f"⚙️ settings: {settings}")
379
-
380
- prediction_length = settings.get("prediction_length")
381
- test_length = settings.get("test_length")
382
-
383
- print(f"📥 Loading Dataset: name={dataset_term}, term={horizon}, storage_path={hf_dataset_root}")
384
- dataset_obj = Dataset(
385
- name=dataset_term,
386
- term=horizon,
387
- prediction_length=prediction_length,
388
- test_length=test_length,
389
- storage_path=hf_dataset_root, # Pass storage path directly
390
- )
391
- print(f"✅ Dataset loaded: {len(dataset_obj.hf_dataset)} series")
392
 
393
- # Get frequency from dataset
394
- dataset_freq = dataset_obj.freq
395
- print(f"📅 Dataset frequency: {dataset_freq}")
 
 
396
 
397
- # Get series names and create mapping
398
- if "item_id" in dataset_obj.hf_dataset.column_names:
399
- series_names = dataset_obj.hf_dataset["item_id"]
400
- else:
401
- series_names = [dataset_obj.hf_dataset[i].get("item_id", f"item_{i}")
402
- for i in range(len(dataset_obj.hf_dataset))]
403
 
404
- print(f"📋 series_names: {list(series_names)}")
405
- series_name_to_idx = {name: idx for idx, name in enumerate(series_names)}
406
  if series in series_name_to_idx:
407
  series_idx = series_name_to_idx[series]
408
  print(f"✅ Found series '{series}' at index {series_idx}")
@@ -410,27 +328,21 @@ def plot_window_series(display_name, series, variate, window_id, horizon, select
410
  series_idx = int(series)
411
  print(f"⚠️ Series '{series}' not found in names, using int index {series_idx}")
412
 
413
- # Get variate names and create mapping
414
- variate_names = dataset_obj.get_variate_names()
415
- print(f"📋 variate_names: {variate_names}")
416
- if variate_names is not None:
417
- # MTS mode: multiple variates per series
418
- variate_name_to_idx = {name: idx for idx, name in enumerate(variate_names)}
419
- if variate in variate_name_to_idx:
420
- variate_idx = variate_name_to_idx[variate]
421
- print(f"✅ Found variate '{variate}' at index {variate_idx}")
422
- else:
423
- variate_idx = int(variate)
424
- print(f"⚠️ Variate '{variate}' not found in names, using int index {variate_idx}")
425
- else:
426
- # UTS mode: each series is a single variate, so variate_idx is always 0
427
  variate_idx = 0
428
  print(f"ℹ️ UTS mode, variate_idx=0")
 
 
 
 
 
 
429
 
430
  if series_idx is None:
431
  series_idx = int(series)
432
  if variate_idx is None:
433
- # For UTS mode, variate_idx should be 0
434
  try:
435
  variate_idx = int(variate) if variate is not None else 0
436
  except (ValueError, TypeError):
 
31
  import gradio as gr
32
  from src.about import DATASET_CHOICES, ALL_MODELS, RESULTS_ROOT, FEATURES_DF, FEATURES_BOOL_DF, PATTERN_MAP
33
  from src.leaderboard import (get_overall_leaderboard, get_dataset_multilevel_leaderboard,
34
+ get_window_leaderboard, get_pattern_leaderboard, resolve_dataset_id,
35
+ _get_dataset_metadata)
36
  from src.about import DATASETS_DF, ALL_HORIZONS
37
+ # get_datasets_root, get_config_root no longer needed here — handled by _get_dataset_metadata
38
  import numpy as np
39
  import pandas as pd
40
  from pathlib import Path
 
44
  import yaml
45
  import tempfile
46
 
47
+ # Dataset, get_dataset_settings, load_dataset_config no longer needed here — handled by _get_dataset_metadata
48
  from src.leaderboard import find_dataset_term_path
49
 
50
 
 
87
  gr.Dropdown(choices=["---"], value="---", label="Select Variate", interactive=True),
88
  )
89
 
90
+ # Use cached metadata (horizon doesn't affect series/variate values)
91
+ metadata = _get_dataset_metadata(dataset_term, "short")
92
+ if metadata is None:
93
+ return (
94
+ gr.Dropdown(choices=["---"], value="---", label="Select Series", interactive=True),
95
+ gr.Dropdown(choices=["---"], value="---", label="Select Variate", interactive=True),
96
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
+ series_list = ["---"] + [str(name) for name in metadata["series_names"]]
 
99
 
100
+ if metadata["is_uts"]:
101
  # UTS mode: variate dropdown should be disabled
102
  return (
103
  gr.Dropdown(choices=series_list, value="---", label="Select Series", interactive=True),
 
105
  )
106
  else:
107
  # MTS mode: both dropdowns are enabled
108
+ variates_list = ["---"] + [str(name) for name in metadata["variate_names"]]
109
  return (
110
  gr.Dropdown(choices=series_list, value="---", label="Select Series", interactive=True),
111
  gr.Dropdown(choices=variates_list, value="---", label="Select Variate", interactive=True),
 
210
  gr.Dropdown(choices=[], value=None, label="Select Testing Window", interactive=False),
211
  )
212
 
213
+ # Use cached metadata
214
+ metadata = _get_dataset_metadata(dataset_term, horizon)
215
+ if metadata is None:
216
+ return (
217
+ gr.Dropdown(choices=[], value=None, label="Select Series", interactive=False),
218
+ gr.Dropdown(choices=[], value=None, label="Select Variate", interactive=False),
219
+ gr.Dropdown(choices=[], value=None, label="Select Testing Window", interactive=False),
220
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
 
222
+ windows = [str(i) for i in range(metadata["num_windows"])]
223
+ series_list = [str(name) for name in metadata["series_names"]]
224
 
225
  # Handle UTS (Univariate Time Series) vs MTS (Multivariate Time Series)
226
+ if metadata["is_uts"]:
227
  # UTS mode: each series is a single variate, so variate is always 0
228
  return (
229
  gr.Dropdown(choices=series_list, value=series_list[0], label="Select Series", interactive=True),
 
232
  )
233
  else:
234
  # MTS mode: multiple variates per series
235
+ variates_list = [str(name) for name in metadata["variate_names"]]
236
  return (
237
  gr.Dropdown(choices=series_list, value=series_list[0], label="Select Series", interactive=True),
238
  gr.Dropdown(choices=variates_list, value=variates_list[0], label="Select Variate", interactive=True),
 
302
  print(f"📊 Applying inverse scale factor: {prediction_scale_factor}")
303
  predictions_quantiles = predictions_quantiles.astype(np.float32) * prediction_scale_factor
304
 
305
+ # Use cached metadata for name-to-index mappings and Dataset object
306
+ metadata = _get_dataset_metadata(dataset_term, horizon)
307
+ if metadata is None:
308
+ print("❌ Failed to load dataset metadata")
309
+ fig = go.Figure()
310
+ fig.update_layout(title="Failed to load dataset metadata")
311
+ return fig, ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
 
313
+ dataset_obj = metadata["dataset_obj"]
314
+ dataset_freq = metadata["freq"]
315
+ test_length = dataset_obj._test_length
316
+ series_name_to_idx = metadata["series_name_to_idx"]
317
+ variate_name_to_idx = metadata["variate_name_to_idx"]
318
 
319
+ print(f"✅ Dataset loaded from cache: {len(dataset_obj.hf_dataset)} series")
320
+ print(f"📅 Dataset frequency: {dataset_freq}")
 
 
 
 
321
 
322
+ # Convert series name to index
323
+ series_idx = None
324
  if series in series_name_to_idx:
325
  series_idx = series_name_to_idx[series]
326
  print(f"✅ Found series '{series}' at index {series_idx}")
 
328
  series_idx = int(series)
329
  print(f"⚠️ Series '{series}' not found in names, using int index {series_idx}")
330
 
331
+ # Convert variate name to index
332
+ variate_idx = None
333
+ if metadata["is_uts"]:
 
 
 
 
 
 
 
 
 
 
 
334
  variate_idx = 0
335
  print(f"ℹ️ UTS mode, variate_idx=0")
336
+ elif variate in variate_name_to_idx:
337
+ variate_idx = variate_name_to_idx[variate]
338
+ print(f"✅ Found variate '{variate}' at index {variate_idx}")
339
+ else:
340
+ variate_idx = int(variate)
341
+ print(f"⚠️ Variate '{variate}' not found in names, using int index {variate_idx}")
342
 
343
  if series_idx is None:
344
  series_idx = int(series)
345
  if variate_idx is None:
 
346
  try:
347
  variate_idx = int(variate) if variate is not None else 0
348
  except (ValueError, TypeError):