@@ -62,17 +62,37 @@ def _lookup_cost(
6262 return None
6363
6464
65- def _cost_or_task (cost_row : dict [str , Any ] | None , key : str , task_cost : float , default : float = 0.0 ) -> float :
65+ def _cost_or_task (cost_row : dict [str , Any ] | None , key : str , default : float = 0.0 ) -> float :
6666 if not cost_row :
67- return task_cost if key in { "cost" , "used_cost" } else default
67+ return default
6868
6969 value = cost_row .get (key )
7070 if value is None :
71- return task_cost if key in { "cost" , "used_cost" } else default
71+ return default
7272
7373 return float (value )
7474
7575
76+ def _summarize_missing_processes (
77+ missing_process_counts : dict [str , int ], preview_limit : int = 3
78+ ) -> tuple [list [dict [str , Any ]], str ]:
79+ ordered = sorted (missing_process_counts .items (), key = lambda item : (- item [1 ], item [0 ]))
80+ preview = [
81+ {"process_short" : process_short , "missing_tasks" : missing_tasks }
82+ for process_short , missing_tasks in ordered [:preview_limit ]
83+ ]
84+ parts = [
85+ f"{ item ['process_short' ]} ({ item ['missing_tasks' ]} )"
86+ if item ["missing_tasks" ] != 1
87+ else item ["process_short" ]
88+ for item in preview
89+ ]
90+ hidden_count = max (len (ordered ) - preview_limit , 0 )
91+ if hidden_count :
92+ parts .append (f"+{ hidden_count } more" )
93+ return preview , ", " .join (parts )
94+
95+
7696def _is_highlight_process (process : str ) -> bool :
7797 process_lc = process .lower ()
7898 return any (keyword in process_lc for keyword in _HIGHLIGHT_KEYWORDS )
@@ -240,9 +260,11 @@ def build_report_data(jsonl_dir: Path, include_failed_runs: bool = False) -> dic
240260 "unused_cost" : 0.0 ,
241261 }
242262
263+ costs_jsonl_path = jsonl_dir / "costs.jsonl"
264+ cur_supplied = costs_jsonl_path .exists ()
243265 costs_index : dict [tuple [str , str , str ], dict [str , Any ]] = {}
244266 has_cost_rows = False
245- for c in _iter_jsonl (jsonl_dir / "costs.jsonl" ):
267+ for c in _iter_jsonl (costs_jsonl_path ):
246268 has_cost_rows = True
247269 run_id = str (c .get ("run_id" , "" ))
248270 process = str (c .get ("process" , "" ))
@@ -289,6 +311,10 @@ def build_report_data(jsonl_dir: Path, include_failed_runs: bool = False) -> dic
289311 task_run_acc : dict [str , dict [str , float ]] = defaultdict (
290312 lambda : {"requested_cpu_h" : 0.0 , "requested_mem_gib_h" : 0.0 , "real_cpu_h" : 0.0 , "real_mem_gib_h" : 0.0 }
291313 )
314+ cost_coverage_runs : dict [tuple [str , str ], dict [str , Any ]] = {}
315+ total_cost_tasks = 0
316+ matched_cost_tasks = 0
317+ missing_cost_tasks = 0
292318
293319 for t in _iter_jsonl (jsonl_dir / "tasks.jsonl" ):
294320 run_id = str (t .get ("run_id" , "" ))
@@ -311,21 +337,40 @@ def build_report_data(jsonl_dir: Path, include_failed_runs: bool = False) -> dic
311337 }
312338
313339 cost_row = _lookup_cost (costs_index , run_id = run_id , process = process , process_short = process_short , hash_short = hash_short )
314- task_cost = float (t .get ("cost" ) or 0.0 )
340+
341+ if cur_supplied :
342+ total_cost_tasks += 1
343+ coverage = cost_coverage_runs .setdefault (
344+ run_group_key ,
345+ {
346+ "run_id" : run_id ,
347+ "group" : group ,
348+ "total_tasks" : 0 ,
349+ "matched_tasks" : 0 ,
350+ "missing_tasks" : 0 ,
351+ "missing_process_counts" : defaultdict (int ),
352+ },
353+ )
354+ coverage ["total_tasks" ] += 1
355+ if cost_row :
356+ matched_cost_tasks += 1
357+ coverage ["matched_tasks" ] += 1
358+ else :
359+ missing_cost_tasks += 1
360+ coverage ["missing_tasks" ] += 1
361+ missing_process = process_short or process or "unknown"
362+ coverage ["missing_process_counts" ][missing_process ] += 1
315363
316364 if cost_row :
317- run_cost_acc [run_group_key ]["cost" ] += _cost_or_task (cost_row , "cost" , task_cost )
318- run_cost_acc [run_group_key ]["used_cost" ] += _cost_or_task (cost_row , "used_cost" , task_cost )
319- run_cost_acc [run_group_key ]["unused_cost" ] += _cost_or_task (cost_row , "unused_cost" , task_cost , default = 0.0 )
320- else :
321- run_cost_acc [run_group_key ]["cost" ] += task_cost
322- run_cost_acc [run_group_key ]["used_cost" ] += task_cost
365+ run_cost_acc [run_group_key ]["cost" ] += _cost_or_task (cost_row , "cost" )
366+ run_cost_acc [run_group_key ]["used_cost" ] += _cost_or_task (cost_row , "used_cost" )
367+ run_cost_acc [run_group_key ]["unused_cost" ] += _cost_or_task (cost_row , "unused_cost" )
323368
324369 if has_cost_rows :
325370 overview_key = (group , process_short )
326- cost_group_acc [overview_key ]["total_cost" ] += _cost_or_task (cost_row , "cost" , task_cost )
327- cost_group_acc [overview_key ]["used_cost" ] += _cost_or_task (cost_row , "used_cost" , task_cost )
328- cost_group_acc [overview_key ]["unused_cost" ] += _cost_or_task (cost_row , "unused_cost" , task_cost , default = 0.0 )
371+ cost_group_acc [overview_key ]["total_cost" ] += _cost_or_task (cost_row , "cost" )
372+ cost_group_acc [overview_key ]["used_cost" ] += _cost_or_task (cost_row , "used_cost" )
373+ cost_group_acc [overview_key ]["unused_cost" ] += _cost_or_task (cost_row , "unused_cost" )
329374 cost_group_acc [overview_key ]["n_tasks" ] += 1
330375
331376 status = t .get ("status" )
@@ -531,6 +576,34 @@ def build_report_data(jsonl_dir: Path, include_failed_runs: bool = False) -> dic
531576 ]
532577 cost_overview .sort (key = lambda x : float (x .get ("total_cost" ) or 0 ), reverse = True )
533578
579+ runs_with_missing_costs = []
580+ for row in cost_coverage_runs .values ():
581+ if int (row ["missing_tasks" ]) <= 0 :
582+ continue
583+ missing_processes , missing_process_summary = _summarize_missing_processes (row ["missing_process_counts" ])
584+ runs_with_missing_costs .append (
585+ {
586+ "run_id" : row ["run_id" ],
587+ "group" : row ["group" ],
588+ "total_tasks" : int (row ["total_tasks" ]),
589+ "matched_tasks" : int (row ["matched_tasks" ]),
590+ "missing_tasks" : int (row ["missing_tasks" ]),
591+ "missing_processes" : missing_processes ,
592+ "missing_process_summary" : missing_process_summary ,
593+ }
594+ )
595+ runs_with_missing_costs .sort (key = lambda row : (- row ["missing_tasks" ], str (row ["group" ]), str (row ["run_id" ])))
596+
597+ cost_coverage = {
598+ "cur_supplied" : cur_supplied ,
599+ "has_any_cost_rows" : has_cost_rows ,
600+ "total_included_tasks" : total_cost_tasks ,
601+ "matched_task_count" : matched_cost_tasks ,
602+ "missing_task_count" : missing_cost_tasks ,
603+ "coverage_pct" : _round ((matched_cost_tasks / total_cost_tasks ) * 100.0 , 1 ) if total_cost_tasks else None ,
604+ "runs_with_missing_costs" : runs_with_missing_costs ,
605+ }
606+
534607 combined_task_runtime = []
535608 for (pipeline , group ), panel_acc in sorted (combined_runtime_acc .items (), key = lambda x : (x [0 ][0 ], x [0 ][1 ])):
536609 process_runtime_ms = panel_acc ["process_runtime_ms" ]
@@ -600,6 +673,7 @@ def build_report_data(jsonl_dir: Path, include_failed_runs: bool = False) -> dic
600673 "task_table" : task_table ,
601674 "task_scatter" : task_scatter ,
602675 "cost_overview" : cost_overview ,
676+ "cost_coverage" : cost_coverage ,
603677 }
604678
605679
0 commit comments