@@ -133,6 +133,70 @@ def get_results_dir():
133133 return results_dir_candidates [1 ] # Fallback to default
134134
135135
136+ # (mtime, value). Each is replaced as a whole so a concurrent reader never sees a
137+ # value that disagrees with the mtime it was built from.
138+ _TRENDS_DF_CACHE = (None , None )
139+ _RUN_DIRS_CACHE = (None , [])
140+
141+
142+ def _mtime (path ):
143+ try :
144+ return os .path .getmtime (path )
145+ except OSError :
146+ return None
147+
148+
149+ def load_trends_df (results_dir ):
150+ """Parsed trends_cache.csv, reread only when the file changes.
151+
152+ Shared by every request in this worker, so callers must not mutate it.
153+ """
154+ global _TRENDS_DF_CACHE
155+
156+ cache_file = os .path .join (results_dir , "trends_cache.csv" )
157+ mtime = _mtime (cache_file )
158+ if mtime is None :
159+ logging .warning (f"Trends cache file not found at { cache_file } " )
160+ return None
161+
162+ cached_mtime , df = _TRENDS_DF_CACHE
163+ if cached_mtime == mtime :
164+ return df
165+
166+ try :
167+ df = pd .read_csv (cache_file )
168+ except Exception as e :
169+ logging .error (f"Error reading trends cache: { e } " )
170+ return None
171+
172+ logging .info (f"Loaded { len (df )} rows from trends cache." )
173+ _TRENDS_DF_CACHE = (mtime , df )
174+ return df
175+
176+
177+ def list_run_dirs (results_dir ):
178+ """Run directory names, restatted only when results_dir gains or loses entries.
179+
180+ Shared by every request in this worker, so callers must not mutate it.
181+ """
182+ global _RUN_DIRS_CACHE
183+
184+ mtime = _mtime (results_dir )
185+ if mtime is None :
186+ return []
187+
188+ cached_mtime , dirs = _RUN_DIRS_CACHE
189+ if cached_mtime == mtime :
190+ return dirs
191+
192+ dirs = [
193+ d for d in os .listdir (results_dir )
194+ if os .path .isdir (os .path .join (results_dir , d ))
195+ ]
196+ _RUN_DIRS_CACHE = (mtime , dirs )
197+ return dirs
198+
199+
136200def get_eval_details (results_dir , dir_name ):
137201 details = {
138202 "product" : "N/A" ,
@@ -228,14 +292,7 @@ def get_color_for_pct(val_str):
228292def on_load (e : me .LoadEvent ):
229293 state = me .state (State )
230294 results_dir = get_results_dir ()
231- directories = []
232- if os .path .exists (results_dir ):
233- # List directories only
234- directories = [
235- d
236- for d in os .listdir (results_dir )
237- if os .path .isdir (os .path .join (results_dir , d ))
238- ]
295+ directories = list_run_dirs (results_dir )
239296
240297 job_id = me .query_params .get ("job_id" ) or me .query_params .get ("jobid" )
241298 if job_id and job_id in directories :
@@ -258,14 +315,8 @@ def on_load(e: me.LoadEvent):
258315
259316def status_component ():
260317 results_dir = get_results_dir ()
261- directories = []
262- if os .path .exists (results_dir ):
263- directories = [
264- d
265- for d in os .listdir (results_dir )
266- if os .path .isdir (os .path .join (results_dir , d ))
267- ]
268-
318+ directories = list_run_dirs (results_dir )
319+
269320 with me .box (
270321 style = me .Style (
271322 background = "#ffffff" ,
@@ -305,10 +356,9 @@ def on_agent_tab_change(e):
305356
306357 # Build summary data from precomputed trends cache
307358 data = []
308- cache_file = os . path . join (results_dir , "trends_cache.csv" )
309- if os . path . exists ( cache_file ) :
359+ cache_df = load_trends_df (results_dir )
360+ if cache_df is not None :
310361 try :
311- cache_df = pd .read_csv (cache_file )
312362 for _ , row in cache_df .iterrows ():
313363 data .append ({
314364 'AI Score' : row ['ai_score' ] if 'ai_score' in row else None ,
@@ -326,7 +376,6 @@ def on_agent_tab_change(e):
326376 except Exception as e :
327377 logging .error (f"Error reading trends cache: { e } " )
328378 else :
329- logging .warning (f"Trends cache file not found at { cache_file } " )
330379 me .text ("Trends cache file not found. Please run precompute." )
331380 return
332381
@@ -545,12 +594,9 @@ def _pct(value):
545594 return f"{ value :.0f} %" if not pd .isna (value ) else "N/A"
546595
547596
548- def _build_summaries (cache_file ):
597+ def _build_summaries (cache_df ):
549598 import re
550599
551- cache_df = pd .read_csv (cache_file )
552- logging .info (f"Loaded { len (cache_df )} rows from trends cache." )
553-
554600 rows = []
555601 for _ , row in cache_df .iterrows ():
556602 score = row ['ai_score' ] if 'ai_score' in row else 0.0
@@ -587,21 +633,22 @@ def load_summaries(results_dir):
587633 """
588634 global _SUMMARIES_CACHE
589635
590- cache_file = os .path .join (results_dir , "trends_cache.csv" )
591- try :
592- mtime = os .path .getmtime (cache_file )
593- except OSError :
594- logging .warning (f"Trends cache file not found at { cache_file } " )
636+ mtime = _mtime (os .path .join (results_dir , "trends_cache.csv" ))
637+ if mtime is None :
595638 return []
596639
597640 cached_mtime , rows = _SUMMARIES_CACHE
598641 if cached_mtime == mtime :
599642 return rows
600643
644+ cache_df = load_trends_df (results_dir )
645+ if cache_df is None :
646+ return []
647+
601648 try :
602- rows = _build_summaries (cache_file )
649+ rows = _build_summaries (cache_df )
603650 except Exception as e :
604- logging .error (f"Error reading trends cache: { e } " )
651+ logging .error (f"Error building list rows from trends cache: { e } " )
605652 return []
606653
607654 _SUMMARIES_CACHE = (mtime , rows )
@@ -2074,15 +2121,8 @@ def render_app_content():
20742121 results_dir = get_results_dir ()
20752122 logging .info (f"render_app_content: selected_directory='{ state .selected_directory } ', selected_evals='{ state .selected_evals } ', selected_main_tab='{ state .selected_main_tab } '" )
20762123
2077- directories = []
2078- if os .path .exists (results_dir ):
2079- # List directories only
2080- directories = [
2081- d
2082- for d in os .listdir (results_dir )
2083- if os .path .isdir (os .path .join (results_dir , d ))
2084- ]
2085-
2124+ directories = list_run_dirs (results_dir )
2125+
20862126 def on_title_click (e : me .ClickEvent ):
20872127 state .selected_directory = ""
20882128 state .conversation_index = 0
@@ -2308,10 +2348,9 @@ def get_val(cfg_name):
23082348 me .text ("AI Summary" , type = "headline-5" )
23092349
23102350 if not state .ai_summary and state .selected_directory :
2311- trends_cache_file = os . path . join (results_dir , "trends_cache.csv" )
2312- if os . path . exists ( trends_cache_file ) :
2351+ cache_df = load_trends_df (results_dir )
2352+ if cache_df is not None :
23132353 try :
2314- cache_df = pd .read_csv (trends_cache_file )
23152354 run_data = cache_df [cache_df ['job_id' ] == state .selected_directory ]
23162355 if not run_data .empty and 'ai_summary' in run_data .columns :
23172356 summary = run_data ['ai_summary' ].values [0 ]
0 commit comments