gani2004 commited on
Commit
ea6eab5
Β·
1 Parent(s): 3852050

Harden task score bounds across all inference edge cases

Browse files
Files changed (1) hide show
  1. inference.py +161 -94
inference.py CHANGED
@@ -77,6 +77,15 @@ def rounded_open_interval_score(value: float, ndigits: int = 4) -> float:
77
  return to_open_interval_score(rounded)
78
 
79
 
 
 
 
 
 
 
 
 
 
80
  class TeeStream:
81
  """Write output to multiple streams (console + log file)."""
82
 
@@ -365,6 +374,11 @@ def log_step(task_id: str, step: int, action: dict, reward: float, done: bool, i
365
  def log_end(task_id: str, reward: float, metadata: Optional[dict] = None):
366
  """Emit an [END] structured log line."""
367
  reward = rounded_open_interval_score(reward, 4)
 
 
 
 
 
368
  entry = {
369
  "task_id": task_id,
370
  "reward": reward,
@@ -399,14 +413,37 @@ def run_evaluation():
399
  task_id = difficulty
400
  available = len(env._task_cases.get(difficulty, []))
401
  num_cases = min(CASES_PER_DIFFICULTY, available)
 
402
 
403
  if num_cases == 0:
404
  print(f" No cases available for {difficulty}")
 
 
 
 
 
 
 
 
 
 
 
 
405
  continue
406
 
407
  # Check timeout before starting a difficulty tier
408
  if _check_timeout():
409
- break
 
 
 
 
 
 
 
 
 
 
410
 
411
  # ── [START] ──
412
  log_start(task_id, {"model": MODEL_NAME, "num_cases": num_cases})
@@ -417,101 +454,131 @@ def run_evaluation():
417
 
418
  difficulty_scores = []
419
 
420
- for i in range(num_cases):
421
- # Check timeout before each case
422
- if _check_timeout():
423
- break
424
-
425
- # Reset environment for this difficulty
426
- obs = env.reset(task_id=difficulty)
427
- print(f"\n Case {i+1}/{num_cases}: {obs.case_id}")
428
-
429
- # Get LLM action
430
- action_dict = call_llm(client, obs)
431
- if action_dict is None:
432
- print(" ⚠ LLM failed, using fallback action")
433
- action_dict = get_fallback_action()
434
-
435
- # Build MedAction
436
- med_action = MedAction(
437
- diagnosis_codes=action_dict["diagnosis_codes"],
438
- procedure_codes=action_dict.get("procedure_codes", []),
439
- decision=action_dict["decision"],
440
- confidence=action_dict["confidence"],
441
- reasoning=action_dict["reasoning"],
442
- modifier_codes=action_dict.get("modifier_codes", []),
443
- risk_flags=action_dict.get("risk_flags", []),
444
- )
445
-
446
- # Step the environment
447
- try:
448
- result_obs = env.step(med_action)
449
- score = to_open_interval_score(result_obs.reward if result_obs.reward is not None else 0.0)
450
- done = result_obs.done if result_obs.done is not None else True
451
- difficulty_scores.append(score)
452
- all_scores.append(score)
453
-
454
- # ── [STEP] ──
455
- log_step(
456
- task_id=task_id,
457
- step=i + 1,
458
- action=action_dict,
459
- reward=rounded_open_interval_score(score, 4),
460
- done=done,
461
- info={
462
- "case_id": obs.case_id,
463
- "feedback": result_obs.feedback if result_obs.feedback else None,
464
- },
465
- )
466
-
467
- print(f" Score: {score:.4f}")
468
- print(f" Decision: {action_dict.get('decision', 'N/A')}")
469
- print(f" Diagnosis: {action_dict.get('diagnosis_codes', [])}")
470
- print(f" Procedure: {action_dict.get('procedure_codes', [])}")
471
-
472
- if result_obs.reward_breakdown:
473
- gc = result_obs.reward_breakdown.get("grade_components", {})
474
- if gc:
475
- print(f" Components: diag={gc.get('diagnosis_accuracy', 0):.2f} "
476
- f"proc={gc.get('procedure_accuracy', 0):.2f} "
477
- f"dec={gc.get('decision_accuracy', 0):.2f}")
478
- pens = result_obs.reward_breakdown.get("penalties", {})
479
- if pens:
480
- print(f" Penalties: {list(pens.keys())}")
481
-
482
- if result_obs.feedback:
483
- print(f" Feedback: {result_obs.feedback}")
484
-
485
- except Exception as e:
486
- print(f" βœ— Step failed: {e}")
487
- fallback_score = to_open_interval_score(0.0)
488
- difficulty_scores.append(fallback_score)
489
- all_scores.append(fallback_score)
490
-
491
- # ── [STEP] with failure ──
492
- log_step(
493
- task_id=task_id,
494
- step=i + 1,
495
- action=action_dict,
496
- reward=fallback_score,
497
- done=True,
498
- info={"error": str(e)},
499
  )
500
 
501
- # Rate limiting
502
- time.sleep(0.5)
503
-
504
- if difficulty_scores:
505
- avg = sum(difficulty_scores) / len(difficulty_scores)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
506
  results_by_difficulty[difficulty] = {
507
- "scores": difficulty_scores,
508
- "average": rounded_open_interval_score(avg, 4),
509
- "count": len(difficulty_scores),
510
  }
511
- print(f"\n {difficulty.upper()} Average: {avg:.4f} ({len(difficulty_scores)} cases)")
512
-
513
- # ── [END] ──
514
- log_end(task_id, round(avg, 4), {"num_cases": len(difficulty_scores)})
 
 
 
515
 
516
  # Final summary
517
  print(f"\n{'=' * 70}")
@@ -525,8 +592,8 @@ def run_evaluation():
525
  overall = sum(all_scores) / len(all_scores)
526
  print(f"\n {'OVERALL':>8}: {overall:.4f} ({len(all_scores)} total cases)")
527
  else:
528
- overall = 0.0
529
- print("\n No scores recorded.")
530
 
531
  elapsed = time.time() - _start_time
532
  print(f"\n Runtime: {elapsed:.1f}s")
 
77
  return to_open_interval_score(rounded)
78
 
79
 
80
+ def is_strict_open_interval(value: float) -> bool:
81
+ """Return True if value is strictly between 0 and 1 and finite."""
82
+ try:
83
+ score = float(value)
84
+ except (TypeError, ValueError):
85
+ return False
86
+ return math.isfinite(score) and 0.0 < score < 1.0
87
+
88
+
89
  class TeeStream:
90
  """Write output to multiple streams (console + log file)."""
91
 
 
374
  def log_end(task_id: str, reward: float, metadata: Optional[dict] = None):
375
  """Emit an [END] structured log line."""
376
  reward = rounded_open_interval_score(reward, 4)
377
+ metadata = dict(metadata or {})
378
+ if not is_strict_open_interval(reward):
379
+ reward = rounded_open_interval_score(0.0, 4)
380
+ metadata["score_sanitized"] = True
381
+
382
  entry = {
383
  "task_id": task_id,
384
  "reward": reward,
 
413
  task_id = difficulty
414
  available = len(env._task_cases.get(difficulty, []))
415
  num_cases = min(CASES_PER_DIFFICULTY, available)
416
+ task_ended = False
417
 
418
  if num_cases == 0:
419
  print(f" No cases available for {difficulty}")
420
+ fallback_task_score = rounded_open_interval_score(0.0, 4)
421
+ results_by_difficulty[difficulty] = {
422
+ "scores": [],
423
+ "average": fallback_task_score,
424
+ "count": 0,
425
+ }
426
+
427
+ # Emit structured task-level logs even when empty, so validators
428
+ # never infer an implicit 0.0 score for a missing task.
429
+ log_start(task_id, {"model": MODEL_NAME, "num_cases": 0})
430
+ log_end(task_id, fallback_task_score, {"num_cases": 0, "skipped": "no_cases"})
431
+ task_ended = True
432
  continue
433
 
434
  # Check timeout before starting a difficulty tier
435
  if _check_timeout():
436
+ print(f" Skipping {difficulty} due to runtime limit.")
437
+ fallback_task_score = rounded_open_interval_score(0.0, 4)
438
+ results_by_difficulty[difficulty] = {
439
+ "scores": [],
440
+ "average": fallback_task_score,
441
+ "count": 0,
442
+ }
443
+ log_start(task_id, {"model": MODEL_NAME, "num_cases": 0})
444
+ log_end(task_id, fallback_task_score, {"num_cases": 0, "skipped": "timeout"})
445
+ task_ended = True
446
+ continue
447
 
448
  # ── [START] ──
449
  log_start(task_id, {"model": MODEL_NAME, "num_cases": num_cases})
 
454
 
455
  difficulty_scores = []
456
 
457
+ try:
458
+ for i in range(num_cases):
459
+ # Check timeout before each case
460
+ if _check_timeout():
461
+ break
462
+
463
+ # Reset environment for this difficulty
464
+ obs = env.reset(task_id=difficulty)
465
+ print(f"\n Case {i+1}/{num_cases}: {obs.case_id}")
466
+
467
+ # Get LLM action
468
+ action_dict = call_llm(client, obs)
469
+ if action_dict is None:
470
+ print(" ⚠ LLM failed, using fallback action")
471
+ action_dict = get_fallback_action()
472
+
473
+ # Build MedAction
474
+ med_action = MedAction(
475
+ diagnosis_codes=action_dict["diagnosis_codes"],
476
+ procedure_codes=action_dict.get("procedure_codes", []),
477
+ decision=action_dict["decision"],
478
+ confidence=action_dict["confidence"],
479
+ reasoning=action_dict["reasoning"],
480
+ modifier_codes=action_dict.get("modifier_codes", []),
481
+ risk_flags=action_dict.get("risk_flags", []),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
482
  )
483
 
484
+ # Step the environment
485
+ try:
486
+ result_obs = env.step(med_action)
487
+ score = to_open_interval_score(result_obs.reward if result_obs.reward is not None else 0.0)
488
+ done = result_obs.done if result_obs.done is not None else True
489
+ difficulty_scores.append(score)
490
+ all_scores.append(score)
491
+
492
+ # ── [STEP] ──
493
+ log_step(
494
+ task_id=task_id,
495
+ step=i + 1,
496
+ action=action_dict,
497
+ reward=rounded_open_interval_score(score, 4),
498
+ done=done,
499
+ info={
500
+ "case_id": obs.case_id,
501
+ "feedback": result_obs.feedback if result_obs.feedback else None,
502
+ },
503
+ )
504
+
505
+ print(f" Score: {score:.4f}")
506
+ print(f" Decision: {action_dict.get('decision', 'N/A')}")
507
+ print(f" Diagnosis: {action_dict.get('diagnosis_codes', [])}")
508
+ print(f" Procedure: {action_dict.get('procedure_codes', [])}")
509
+
510
+ if result_obs.reward_breakdown:
511
+ gc = result_obs.reward_breakdown.get("grade_components", {})
512
+ if gc:
513
+ print(f" Components: diag={gc.get('diagnosis_accuracy', 0):.2f} "
514
+ f"proc={gc.get('procedure_accuracy', 0):.2f} "
515
+ f"dec={gc.get('decision_accuracy', 0):.2f}")
516
+ pens = result_obs.reward_breakdown.get("penalties", {})
517
+ if pens:
518
+ print(f" Penalties: {list(pens.keys())}")
519
+
520
+ if result_obs.feedback:
521
+ print(f" Feedback: {result_obs.feedback}")
522
+
523
+ except Exception as e:
524
+ print(f" βœ— Step failed: {e}")
525
+ fallback_score = to_open_interval_score(0.0)
526
+ difficulty_scores.append(fallback_score)
527
+ all_scores.append(fallback_score)
528
+
529
+ # ── [STEP] with failure ──
530
+ log_step(
531
+ task_id=task_id,
532
+ step=i + 1,
533
+ action=action_dict,
534
+ reward=fallback_score,
535
+ done=True,
536
+ info={"error": str(e)},
537
+ )
538
+
539
+ # Rate limiting
540
+ time.sleep(0.5)
541
+
542
+ if difficulty_scores:
543
+ avg = sum(difficulty_scores) / len(difficulty_scores)
544
+ normalized_avg = rounded_open_interval_score(avg, 4)
545
+ results_by_difficulty[difficulty] = {
546
+ "scores": difficulty_scores,
547
+ "average": normalized_avg,
548
+ "count": len(difficulty_scores),
549
+ }
550
+ print(f"\n {difficulty.upper()} Average: {avg:.4f} ({len(difficulty_scores)} cases)")
551
+
552
+ # ── [END] ──
553
+ log_end(task_id, normalized_avg, {"num_cases": len(difficulty_scores)})
554
+ task_ended = True
555
+ else:
556
+ # If a tier is interrupted before any scored step, still emit
557
+ # a valid task score inside (0, 1) to satisfy strict validators.
558
+ fallback_task_score = rounded_open_interval_score(0.0, 4)
559
+ results_by_difficulty[difficulty] = {
560
+ "scores": [],
561
+ "average": fallback_task_score,
562
+ "count": 0,
563
+ }
564
+ print(f"\n {difficulty.upper()} Average: {fallback_task_score:.4f} (0 cases)")
565
+ log_end(task_id, fallback_task_score, {"num_cases": 0, "skipped": "no_scored_cases"})
566
+ task_ended = True
567
+ except Exception as difficulty_error:
568
+ print(f"\n βœ— Difficulty '{difficulty}' failed unexpectedly: {difficulty_error}")
569
+ fallback_task_score = rounded_open_interval_score(0.0, 4)
570
  results_by_difficulty[difficulty] = {
571
+ "scores": [],
572
+ "average": fallback_task_score,
573
+ "count": 0,
574
  }
575
+ if not task_ended:
576
+ log_end(
577
+ task_id,
578
+ fallback_task_score,
579
+ {"num_cases": 0, "skipped": "difficulty_exception", "error": str(difficulty_error)},
580
+ )
581
+ task_ended = True
582
 
583
  # Final summary
584
  print(f"\n{'=' * 70}")
 
592
  overall = sum(all_scores) / len(all_scores)
593
  print(f"\n {'OVERALL':>8}: {overall:.4f} ({len(all_scores)} total cases)")
594
  else:
595
+ overall = rounded_open_interval_score(0.0, 4)
596
+ print("\n No scores recorded; using safe fallback overall score.")
597
 
598
  elapsed = time.time() - _start_time
599
  print(f"\n Runtime: {elapsed:.1f}s")