@@ -118,11 +118,11 @@ def _apply_mutate(qty: float, mutate: str) -> float:
118118 elif mutate_upper == "FLOOR" :
119119 return math .floor (qty )
120120 elif mutate_upper == "NUMBOOL" :
121- # If qty equals 0, leave it at 0. Else, set it to 1.
121+ # If qty near 0, set it at 0. Else, set it to 1.
122122 return 0.0 if abs (qty ) < 1e-9 else 1.0
123123 elif mutate_upper == "NOTNUMBOOL" :
124- # If qty equals 0, set it to 1. Else, set it to 0.
125- return 1.0 if qty == 0 else 0.0
124+ # If qty near 0, set it to 1. Else, set it to 0.
125+ return 1.0 if abs ( qty ) < 1e-9 else 0.0
126126 else : # NONE or any unrecognized value
127127 return qty
128128
@@ -220,6 +220,11 @@ def aggregate_rates_by_type(
220220
221221
222222def build_summary (pairs : list [tuple [str , str ]]) -> dict [str , Any ]:
223+ # Early exit if no pairs
224+ if not pairs :
225+ print ("Error: No log entries to summarize" , file = sys .stderr )
226+ sys .exit (1 )
227+
223228 log_count = len (pairs )
224229 per_ts = Counter (ts for ts , _ in pairs )
225230 n_ts = len (per_ts )
@@ -228,31 +233,29 @@ def build_summary(pairs: list[tuple[str, str]]) -> dict[str, Any]:
228233 if counts and len (set (counts )) > 1 :
229234 mps = "ERROR"
230235
231- if pairs :
232- first = json .loads (pairs [0 ][1 ])
233- last = json .loads (pairs [- 1 ][1 ])
234- time_block = {
235- "begin_step" : {
236- "nanosec" : int (pairs [0 ][0 ]),
237- "begin" : first .get ("start" ),
238- "end" : first .get ("end" ),
239- },
240- "end_step" : {
241- "nanosec" : int (pairs [- 1 ][0 ]),
242- "begin" : last .get ("start" ),
243- "end" : last .get ("end" ),
244- },
245- }
246- else :
247- empty = {"nanosec" : None , "begin" : None , "end" : None }
248- time_block = {"begin_step" : empty .copy (), "end_step" : empty .copy ()}
236+ # Parse first and last entries (guaranteed to exist after early exit check)
237+ first = json .loads (pairs [0 ][1 ])
238+ last = json .loads (pairs [- 1 ][1 ])
239+
240+ time_block = {
241+ "begin_step" : {
242+ "nanosec" : int (pairs [0 ][0 ]),
243+ "begin" : first .get ("start" ),
244+ "end" : first .get ("end" ),
245+ },
246+ "end_step" : {
247+ "nanosec" : int (pairs [- 1 ][0 ]),
248+ "begin" : last .get ("start" ),
249+ "end" : last .get ("end" ),
250+ },
251+ }
249252
250253 # Get aggregated data by type
251254 by_types , total_r , qty_by_types = aggregate_rates_by_type (pairs )
252255
253256 # Get overall time range for by_type entries
254- begin_time = first .get ("start" ) if pairs else None
255- end_time = last .get ("end" ) if pairs else None
257+ begin_time = first .get ("start" )
258+ end_time = last .get ("end" )
256259
257260 # Build flat list of entries
258261 rate_list = []
@@ -292,6 +295,31 @@ def write_yaml(path: Path, doc: dict[str, Any]) -> None:
292295 )
293296
294297
298+ def _str_to_bool (value : str ) -> bool :
299+ """
300+ Convert string to boolean.
301+
302+ Args:
303+ value: String representation of boolean.
304+
305+ Returns:
306+ Boolean value.
307+
308+ Raises:
309+ argparse.ArgumentTypeError: If value cannot be converted.
310+ """
311+ if isinstance (value , bool ):
312+ return value
313+ if value .lower () in ('yes' , 'true' , 't' , 'y' , '1' ):
314+ return True
315+ elif value .lower () in ('no' , 'false' , 'f' , 'n' , '0' ):
316+ return False
317+ else :
318+ raise argparse .ArgumentTypeError (
319+ f"Boolean value expected. Got: { value } "
320+ )
321+
322+
295323def main () -> None :
296324 parser = argparse .ArgumentParser (
297325 description = (
@@ -311,10 +339,12 @@ def main() -> None:
311339 )
312340 parser .add_argument (
313341 "--debug" ,
314- action = "store_true" ,
342+ type = _str_to_bool ,
343+ default = False ,
344+ metavar = "BOOL" ,
315345 help = (
316346 "Enable debug mode: write <stem>_diff.txt with one "
317- "[ts,log] JSON per line."
347+ "[ts,log] JSON per line (true/false) ."
318348 ),
319349 )
320350 parser .add_argument (
@@ -323,8 +353,7 @@ def main() -> None:
323353 default = None ,
324354 metavar = "DIR" ,
325355 help = (
326- "Directory for debug output. If not specified, uses the "
327- "directory from -o output path."
356+ "Directory for debug output. Required when --debug is enabled."
328357 ),
329358 )
330359 args = parser .parse_args ()
@@ -338,9 +367,14 @@ def main() -> None:
338367 pairs = extract_and_sort (args .json )
339368
340369 if args .debug :
341- # Determine debug directory: use --debug_dir if provided,
342- # otherwise use output directory
343- debug_dir = args .debug_dir if args .debug_dir else out_path .parent
370+ # Require debug directory when debug mode is enabled
371+ if not args .debug_dir :
372+ print (
373+ "Error: --debug_dir is required when --debug is enabled" ,
374+ file = sys .stderr
375+ )
376+ sys .exit (1 )
377+ debug_dir = args .debug_dir
344378 debug_dir .mkdir (parents = True , exist_ok = True )
345379 dbg_file = debug_dir / f"{ args .json .stem } _diff.txt"
346380 with dbg_file .open ("w" , encoding = "utf-8" ) as f :
@@ -350,13 +384,6 @@ def main() -> None:
350384 doc = build_summary (pairs )
351385 write_yaml (out_path , doc )
352386
353- if doc ["data_summary" ]["metrics_per_step" ] == "ERROR" :
354- per_ts = Counter (ts for ts , _ in pairs )
355- exp = next (iter (per_ts .values ()), 0 )
356- for ts in sorted (per_ts , key = int ):
357- if per_ts [ts ] != exp :
358- print (ts , per_ts [ts ], file = sys .stdout )
359-
360387
361388if __name__ == "__main__" :
362389 main ()
0 commit comments