Spaces:
Sleeping
Sleeping
Download reward_function.py from RajTheBuilder/MediGuard-AI: direct link, hf CLI and curl.
- Browser
- Download file 5.57 kB
-
https://huggingface.co/spaces/RajTheBuilder/MediGuard-AI/resolve/main/reward_function.py
- Command line
-
hf download hf://spaces/RajTheBuilder/MediGuard-AI/reward_function.py
-
curl -L -o reward_function.py https://huggingface.co/spaces/RajTheBuilder/MediGuard-AI/resolve/main/reward_function.py
5.57 kB
| """ | |
| Reward Function for MediGuard-AI | |
| ================================ | |
| Maps (action Γ patient_condition Γ activity) to a reward signal with: | |
| - Base reward table | |
| - Activity context multipliers (the novel mechanic) | |
| - Alarm fatigue modifier | |
| - Personalization bonus | |
| """ | |
| from enum import Enum | |
| class Action(Enum): | |
| IGNORE = 0 | |
| VERIFY = 1 | |
| ALERT = 2 | |
| class PatientCondition(Enum): | |
| STABLE = "stable" | |
| BORDERLINE = "borderline" | |
| EMERGENCY = "emergency" | |
| DRUG_MASKED = "drug_masked" | |
| # Base reward table: REWARD_TABLE[action][condition] | |
| REWARD_TABLE = { | |
| Action.ALERT: { | |
| PatientCondition.EMERGENCY: +1.0, | |
| PatientCondition.BORDERLINE: +0.2, | |
| PatientCondition.STABLE: -0.5, | |
| PatientCondition.DRUG_MASKED: +1.0, | |
| }, | |
| Action.VERIFY: { | |
| PatientCondition.EMERGENCY: +0.3, | |
| PatientCondition.BORDERLINE: +0.7, | |
| PatientCondition.STABLE: -0.1, | |
| PatientCondition.DRUG_MASKED: +0.3, | |
| }, | |
| Action.IGNORE: { | |
| PatientCondition.EMERGENCY: -1.0, | |
| PatientCondition.BORDERLINE: -0.2, | |
| PatientCondition.STABLE: +0.2, | |
| PatientCondition.DRUG_MASKED: -1.0, | |
| }, | |
| } | |
| # Activity context multipliers β the key insight: | |
| # Same vital sign means different things depending on what the patient is doing. | |
| # HR 130 while walking β expected (low multiplier, discount the anomaly) | |
| # HR 130 while lying still β emergency (high multiplier, amplify the anomaly) | |
| ACTIVITY_CONTEXT = { | |
| 0: 1.00, # resting (lying in bed) β baseline, no discount | |
| 1: 0.40, # eating β slight HR/BP increase expected | |
| 2: 0.50, # walking/ambulating β elevated vitals expected | |
| 3: 1.25, # distressed β amplify concern | |
| 4: 1.60, # falling β immediate concern | |
| } | |
| # Alarm fatigue settings | |
| FATIGUE_WINDOW = 30 # look at last 30 steps | |
| FATIGUE_THRESHOLD = 5 # more than 5 alerts in the window | |
| FATIGUE_MULTIPLIER = 0.6 # reduce reward to 60% | |
| # Personalization settings | |
| PERSONALIZATION_STEP = 200 # kicks in after this many steps | |
| PERSONALIZATION_BONUS = 0.2 # bonus for correctly ignoring known-normal | |
| class RewardFunction: | |
| """ | |
| Stateful reward calculator. | |
| Tracks action history for alarm fatigue and personalization bonuses. | |
| """ | |
| def __init__(self): | |
| self.action_history = [] | |
| self.condition_history = [] | |
| self.activity_history = [] | |
| self.step_count = 0 | |
| def reset(self): | |
| """Clear all history for a new episode.""" | |
| self.action_history = [] | |
| self.condition_history = [] | |
| self.activity_history = [] | |
| self.step_count = 0 | |
| def compute(self, action: Action, condition: PatientCondition, | |
| activity: int = 0) -> float: | |
| """ | |
| Compute the reward for a single (action, condition, activity) tuple. | |
| Parameters | |
| ---------- | |
| action : Action | |
| The agent's action (IGNORE, VERIFY, ALERT). | |
| condition : PatientCondition | |
| The patient's current condition. | |
| activity : int | |
| The patient's current activity code (0-4). | |
| Returns | |
| ------- | |
| float | |
| The reward value (can be negative). | |
| """ | |
| self.step_count += 1 | |
| self.action_history.append(action) | |
| self.condition_history.append(condition) | |
| self.activity_history.append(activity) | |
| # 1. Base reward from the table | |
| base_reward = REWARD_TABLE[action][condition] | |
| # 2. Activity context multiplier | |
| # Only apply to penalty situations β don't discount correct actions | |
| ctx = ACTIVITY_CONTEXT.get(activity, 1.0) | |
| if base_reward < 0: | |
| # Penalties are reduced during expected-high-vitals activities | |
| # e.g., alerting during walking gets less penalty (ctx=0.5) | |
| base_reward *= ctx | |
| elif condition in (PatientCondition.EMERGENCY, PatientCondition.DRUG_MASKED): | |
| # Correct emergency responses amplified during dangerous activities | |
| base_reward *= ctx | |
| # 3. Alarm fatigue modifier | |
| fatigue_modifier = 1.0 | |
| if len(self.action_history) >= FATIGUE_WINDOW: | |
| recent_alerts = sum( | |
| 1 for a in self.action_history[-FATIGUE_WINDOW:] | |
| if a == Action.ALERT | |
| ) | |
| if recent_alerts > FATIGUE_THRESHOLD: | |
| fatigue_modifier = FATIGUE_MULTIPLIER | |
| # 4. Personalization bonus | |
| personalization = 0.0 | |
| if (self.step_count > PERSONALIZATION_STEP | |
| and action == Action.IGNORE | |
| and condition == PatientCondition.STABLE): | |
| personalization = PERSONALIZATION_BONUS | |
| # Final reward | |
| reward = base_reward * fatigue_modifier + personalization | |
| return reward | |
| def get_stats(self) -> dict: | |
| """Return episode statistics for graders.""" | |
| total_alerts = sum(1 for a in self.action_history if a == Action.ALERT) | |
| total_verifies = sum(1 for a in self.action_history if a == Action.VERIFY) | |
| total_ignores = sum(1 for a in self.action_history if a == Action.IGNORE) | |
| return { | |
| "total_steps": self.step_count, | |
| "total_alerts": total_alerts, | |
| "total_verifies": total_verifies, | |
| "total_ignores": total_ignores, | |
| "action_history": list(self.action_history), | |
| "condition_history": list(self.condition_history), | |
| "activity_history": list(self.activity_history), | |
| } | |