Spaces:
Sleeping
Sleeping
Fix score range validation and update deployment config
Browse files- inference.py +34 -3
- privexa_medcodel +1 -0
- server/my_env_environment.py +28 -6
inference.py
CHANGED
|
@@ -12,6 +12,7 @@ Usage:
|
|
| 12 |
"""
|
| 13 |
|
| 14 |
import json
|
|
|
|
| 15 |
import os
|
| 16 |
import re
|
| 17 |
import signal
|
|
@@ -47,6 +48,23 @@ MAX_RETRIES = 2
|
|
| 47 |
# Global timeout safety (inference must complete in < 20 minutes)
|
| 48 |
MAX_RUNTIME_SECONDS = int(os.environ.get("MAX_RUNTIME_SECONDS", "1100")) # ~18.3 min
|
| 49 |
_start_time = time.time()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
|
| 52 |
class TeeStream:
|
|
@@ -321,6 +339,7 @@ def log_start(task_id: str, metadata: Optional[dict] = None):
|
|
| 321 |
|
| 322 |
def log_step(task_id: str, step: int, action: dict, reward: float, done: bool, info: Optional[dict] = None):
|
| 323 |
"""Emit a [STEP] structured log line."""
|
|
|
|
| 324 |
entry = {
|
| 325 |
"task_id": task_id,
|
| 326 |
"step": step,
|
|
@@ -335,6 +354,7 @@ def log_step(task_id: str, step: int, action: dict, reward: float, done: bool, i
|
|
| 335 |
|
| 336 |
def log_end(task_id: str, reward: float, metadata: Optional[dict] = None):
|
| 337 |
"""Emit an [END] structured log line."""
|
|
|
|
| 338 |
entry = {
|
| 339 |
"task_id": task_id,
|
| 340 |
"reward": reward,
|
|
@@ -416,7 +436,7 @@ def run_evaluation():
|
|
| 416 |
# Step the environment
|
| 417 |
try:
|
| 418 |
result_obs = env.step(med_action)
|
| 419 |
-
score = result_obs.reward if result_obs.reward is not None else 0.0
|
| 420 |
done = result_obs.done if result_obs.done is not None else True
|
| 421 |
difficulty_scores.append(score)
|
| 422 |
all_scores.append(score)
|
|
@@ -454,8 +474,19 @@ def run_evaluation():
|
|
| 454 |
|
| 455 |
except Exception as e:
|
| 456 |
print(f" β Step failed: {e}")
|
| 457 |
-
|
| 458 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 459 |
|
| 460 |
# ββ [STEP] with failure ββ
|
| 461 |
log_step(
|
|
|
|
| 12 |
"""
|
| 13 |
|
| 14 |
import json
|
| 15 |
+
import math
|
| 16 |
import os
|
| 17 |
import re
|
| 18 |
import signal
|
|
|
|
| 48 |
# Global timeout safety (inference must complete in < 20 minutes)
|
| 49 |
MAX_RUNTIME_SECONDS = int(os.environ.get("MAX_RUNTIME_SECONDS", "1100")) # ~18.3 min
|
| 50 |
_start_time = time.time()
|
| 51 |
+
SCORE_EPSILON = 1e-4
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def to_open_interval_score(value: float) -> float:
|
| 55 |
+
"""Map scores to strict open interval (0, 1) for validator compliance."""
|
| 56 |
+
try:
|
| 57 |
+
score = float(value)
|
| 58 |
+
except (TypeError, ValueError):
|
| 59 |
+
score = 0.0
|
| 60 |
+
|
| 61 |
+
if not math.isfinite(score):
|
| 62 |
+
score = 0.0
|
| 63 |
+
if score <= 0.0:
|
| 64 |
+
return SCORE_EPSILON
|
| 65 |
+
if score >= 1.0:
|
| 66 |
+
return 1.0 - SCORE_EPSILON
|
| 67 |
+
return score
|
| 68 |
|
| 69 |
|
| 70 |
class TeeStream:
|
|
|
|
| 339 |
|
| 340 |
def log_step(task_id: str, step: int, action: dict, reward: float, done: bool, info: Optional[dict] = None):
|
| 341 |
"""Emit a [STEP] structured log line."""
|
| 342 |
+
reward = round(to_open_interval_score(reward), 4)
|
| 343 |
entry = {
|
| 344 |
"task_id": task_id,
|
| 345 |
"step": step,
|
|
|
|
| 354 |
|
| 355 |
def log_end(task_id: str, reward: float, metadata: Optional[dict] = None):
|
| 356 |
"""Emit an [END] structured log line."""
|
| 357 |
+
reward = round(to_open_interval_score(reward), 4)
|
| 358 |
entry = {
|
| 359 |
"task_id": task_id,
|
| 360 |
"reward": reward,
|
|
|
|
| 436 |
# Step the environment
|
| 437 |
try:
|
| 438 |
result_obs = env.step(med_action)
|
| 439 |
+
score = to_open_interval_score(result_obs.reward if result_obs.reward is not None else 0.0)
|
| 440 |
done = result_obs.done if result_obs.done is not None else True
|
| 441 |
difficulty_scores.append(score)
|
| 442 |
all_scores.append(score)
|
|
|
|
| 474 |
|
| 475 |
except Exception as e:
|
| 476 |
print(f" β Step failed: {e}")
|
| 477 |
+
fallback_score = to_open_interval_score(0.0)
|
| 478 |
+
difficulty_scores.append(fallback_score)
|
| 479 |
+
all_scores.append(fallback_score)
|
| 480 |
+
|
| 481 |
+
# ββ [STEP] with failure ββ
|
| 482 |
+
log_step(
|
| 483 |
+
task_id=task_id,
|
| 484 |
+
step=i + 1,
|
| 485 |
+
action=action_dict,
|
| 486 |
+
reward=fallback_score,
|
| 487 |
+
done=True,
|
| 488 |
+
info={"error": str(e)},
|
| 489 |
+
)
|
| 490 |
|
| 491 |
# ββ [STEP] with failure ββ
|
| 492 |
log_step(
|
privexa_medcodel
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
Subproject commit 8245662c88fe44d8cf65bd2b71ecae28d4f5216c
|
server/my_env_environment.py
CHANGED
|
@@ -18,6 +18,7 @@ Contains: environment logic, deterministic grader, shaped rewards, action valida
|
|
| 18 |
"""
|
| 19 |
|
| 20 |
import json
|
|
|
|
| 21 |
import os
|
| 22 |
import re
|
| 23 |
from typing import Any, Dict, List, Optional, Set, Tuple
|
|
@@ -56,6 +57,23 @@ def _load_task_cases(difficulty: str) -> List[Dict[str, Any]]:
|
|
| 56 |
ICD10_PATTERN = re.compile(r"^[A-Z]\d{2}(\.\d{1,4})?$", re.IGNORECASE)
|
| 57 |
CPT_PATTERN = re.compile(r"^\d{5}$")
|
| 58 |
HCPCS_PATTERN = re.compile(r"^[A-Z]\d{4}$", re.IGNORECASE)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
|
| 61 |
def _validate_action(action_dict: dict) -> Tuple[bool, List[str]]:
|
|
@@ -213,7 +231,7 @@ def _grade(action_dict: dict, ground_truth: dict) -> Dict[str, float]:
|
|
| 213 |
)
|
| 214 |
|
| 215 |
return {
|
| 216 |
-
"score": round(
|
| 217 |
"diagnosis_accuracy": round(diag_score, 4),
|
| 218 |
"procedure_accuracy": round(proc_score, 4),
|
| 219 |
"decision_accuracy": round(dec_score, 4),
|
|
@@ -277,7 +295,7 @@ def _compute_reward(action_dict: dict, ground_truth: dict, difficulty: str = "ea
|
|
| 277 |
diff_mult = {"easy": 0.8, "medium": 1.0, "hard": 1.2}.get(difficulty, 1.0)
|
| 278 |
total_penalty = sum(penalties.values()) * diff_mult
|
| 279 |
total_bonus = sum(bonuses.values())
|
| 280 |
-
final =
|
| 281 |
|
| 282 |
feedback_parts = []
|
| 283 |
if penalties:
|
|
@@ -413,7 +431,11 @@ class MyEnvironment(Environment):
|
|
| 413 |
|
| 414 |
self._current_case = self._pick_case(task_id)
|
| 415 |
|
| 416 |
-
return self._build_observation(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 417 |
|
| 418 |
def step(self, action: MedAction) -> MedObservation: # type: ignore[override]
|
| 419 |
"""
|
|
@@ -429,7 +451,7 @@ class MyEnvironment(Environment):
|
|
| 429 |
return self._build_observation(
|
| 430 |
self._current_case or {},
|
| 431 |
done=True,
|
| 432 |
-
reward=0.0,
|
| 433 |
feedback="Episode already done. Call reset().",
|
| 434 |
)
|
| 435 |
|
|
@@ -456,7 +478,7 @@ class MyEnvironment(Environment):
|
|
| 456 |
return self._build_observation(
|
| 457 |
self._current_case or {},
|
| 458 |
done=self._done,
|
| 459 |
-
reward=0.0,
|
| 460 |
feedback=f"Invalid action: {'; '.join(errors)}",
|
| 461 |
)
|
| 462 |
|
|
@@ -467,7 +489,7 @@ class MyEnvironment(Environment):
|
|
| 467 |
self._action_history.append({"action": action_dict, "valid": True})
|
| 468 |
self._done = True # single-step episode for valid actions
|
| 469 |
|
| 470 |
-
score = reward_result["score"]
|
| 471 |
|
| 472 |
return self._build_observation(
|
| 473 |
self._current_case or {},
|
|
|
|
| 18 |
"""
|
| 19 |
|
| 20 |
import json
|
| 21 |
+
import math
|
| 22 |
import os
|
| 23 |
import re
|
| 24 |
from typing import Any, Dict, List, Optional, Set, Tuple
|
|
|
|
| 57 |
ICD10_PATTERN = re.compile(r"^[A-Z]\d{2}(\.\d{1,4})?$", re.IGNORECASE)
|
| 58 |
CPT_PATTERN = re.compile(r"^\d{5}$")
|
| 59 |
HCPCS_PATTERN = re.compile(r"^[A-Z]\d{4}$", re.IGNORECASE)
|
| 60 |
+
SCORE_EPSILON = 1e-4
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def _to_open_interval_score(value: float) -> float:
|
| 64 |
+
"""Map a score to the strict open interval (0, 1)."""
|
| 65 |
+
try:
|
| 66 |
+
score = float(value)
|
| 67 |
+
except (TypeError, ValueError):
|
| 68 |
+
score = 0.0
|
| 69 |
+
|
| 70 |
+
if not math.isfinite(score):
|
| 71 |
+
score = 0.0
|
| 72 |
+
if score <= 0.0:
|
| 73 |
+
return SCORE_EPSILON
|
| 74 |
+
if score >= 1.0:
|
| 75 |
+
return 1.0 - SCORE_EPSILON
|
| 76 |
+
return score
|
| 77 |
|
| 78 |
|
| 79 |
def _validate_action(action_dict: dict) -> Tuple[bool, List[str]]:
|
|
|
|
| 231 |
)
|
| 232 |
|
| 233 |
return {
|
| 234 |
+
"score": round(_to_open_interval_score(total), 4),
|
| 235 |
"diagnosis_accuracy": round(diag_score, 4),
|
| 236 |
"procedure_accuracy": round(proc_score, 4),
|
| 237 |
"decision_accuracy": round(dec_score, 4),
|
|
|
|
| 295 |
diff_mult = {"easy": 0.8, "medium": 1.0, "hard": 1.2}.get(difficulty, 1.0)
|
| 296 |
total_penalty = sum(penalties.values()) * diff_mult
|
| 297 |
total_bonus = sum(bonuses.values())
|
| 298 |
+
final = _to_open_interval_score(base + total_penalty + total_bonus)
|
| 299 |
|
| 300 |
feedback_parts = []
|
| 301 |
if penalties:
|
|
|
|
| 431 |
|
| 432 |
self._current_case = self._pick_case(task_id)
|
| 433 |
|
| 434 |
+
return self._build_observation(
|
| 435 |
+
self._current_case,
|
| 436 |
+
done=False,
|
| 437 |
+
reward=_to_open_interval_score(0.0),
|
| 438 |
+
)
|
| 439 |
|
| 440 |
def step(self, action: MedAction) -> MedObservation: # type: ignore[override]
|
| 441 |
"""
|
|
|
|
| 451 |
return self._build_observation(
|
| 452 |
self._current_case or {},
|
| 453 |
done=True,
|
| 454 |
+
reward=_to_open_interval_score(0.0),
|
| 455 |
feedback="Episode already done. Call reset().",
|
| 456 |
)
|
| 457 |
|
|
|
|
| 478 |
return self._build_observation(
|
| 479 |
self._current_case or {},
|
| 480 |
done=self._done,
|
| 481 |
+
reward=_to_open_interval_score(0.0),
|
| 482 |
feedback=f"Invalid action: {'; '.join(errors)}",
|
| 483 |
)
|
| 484 |
|
|
|
|
| 489 |
self._action_history.append({"action": action_dict, "valid": True})
|
| 490 |
self._done = True # single-step episode for valid actions
|
| 491 |
|
| 492 |
+
score = _to_open_interval_score(reward_result["score"])
|
| 493 |
|
| 494 |
return self._build_observation(
|
| 495 |
self._current_case or {},
|