Spaces:
Sleeping
Sleeping
joshua400 commited on
Commit Β·
39b98d6
1
Parent(s): f5a4e89
π§ FIX: Fixed simulation progression loop, added phase-aware policies, and restored action history tracking
Browse files- inference.py +39 -37
- server/app.py +12 -48
- server/fairrecovery_environment.py +1 -0
inference.py
CHANGED
|
@@ -1,55 +1,57 @@
|
|
| 1 |
"""
|
| 2 |
FairRecovery++ β Baseline Inference Script.
|
| 3 |
|
| 4 |
-
Updated
|
| 5 |
"""
|
| 6 |
|
| 7 |
from __future__ import annotations
|
| 8 |
-
import argparse
|
| 9 |
-
import json
|
| 10 |
import random
|
| 11 |
-
import numpy as np
|
| 12 |
-
from typing import Dict, List
|
| 13 |
-
|
| 14 |
from fairrecovery_env.models import ResourceAllocation, FairRecoveryAction, FairRecoveryObservation
|
| 15 |
-
from fairrecovery_env.constants import ActionType, ResourceType
|
| 16 |
-
|
| 17 |
-
# Local dummy client for testing if server is not running
|
| 18 |
-
class LocalInference:
|
| 19 |
-
def __init__(self, base_url: str):
|
| 20 |
-
self.base_url = base_url
|
| 21 |
|
| 22 |
-
def
|
| 23 |
-
"""
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
|
|
|
| 28 |
|
| 29 |
def greedy_policy(obs: FairRecoveryObservation) -> FairRecoveryAction:
|
| 30 |
-
"""
|
| 31 |
if obs.day > 10: return FairRecoveryAction(action_type=ActionType.SUBMIT)
|
| 32 |
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
def fairness_aware_policy(obs: FairRecoveryObservation) -> FairRecoveryAction:
|
| 43 |
-
"""Fairness-
|
| 44 |
if obs.day > 10: return FairRecoveryAction(action_type=ActionType.SUBMIT)
|
| 45 |
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
-
return FairRecoveryAction(
|
| 50 |
-
action_type=ActionType.ALLOCATE,
|
| 51 |
-
allocations=[
|
| 52 |
-
ResourceAllocation(zone=vulnerable_zone, resource=ResourceType.MEDICAL)
|
| 53 |
-
],
|
| 54 |
-
reasoning=f"Fair: prioritizing zone {vulnerable_zone} due to vulnerability."
|
| 55 |
-
)
|
|
|
|
| 1 |
"""
|
| 2 |
FairRecovery++ β Baseline Inference Script.
|
| 3 |
|
| 4 |
+
Updated with Phase-Aware policies to correctly advance days.
|
| 5 |
"""
|
| 6 |
|
| 7 |
from __future__ import annotations
|
|
|
|
|
|
|
| 8 |
import random
|
|
|
|
|
|
|
|
|
|
| 9 |
from fairrecovery_env.models import ResourceAllocation, FairRecoveryAction, FairRecoveryObservation
|
| 10 |
+
from fairrecovery_env.constants import ActionType, ResourceType
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
+
def _get_phase_action(obs: FairRecoveryObservation) -> ActionType:
|
| 13 |
+
"""Determine the correct action type based on the 3-phase cycle."""
|
| 14 |
+
num_steps = len(obs.action_history)
|
| 15 |
+
cycle_pos = num_steps % 3
|
| 16 |
+
if cycle_pos == 0: return ActionType.ANALYZE
|
| 17 |
+
if cycle_pos == 1: return ActionType.ALLOCATE
|
| 18 |
+
return ActionType.EXECUTE
|
| 19 |
|
| 20 |
def greedy_policy(obs: FairRecoveryObservation) -> FairRecoveryAction:
|
| 21 |
+
"""Greedy: Target zone 0 (easiest/wealthiest) regardless of vulnerability."""
|
| 22 |
if obs.day > 10: return FairRecoveryAction(action_type=ActionType.SUBMIT)
|
| 23 |
|
| 24 |
+
action_type = _get_phase_action(obs)
|
| 25 |
+
|
| 26 |
+
if action_type == ActionType.ANALYZE:
|
| 27 |
+
return FairRecoveryAction(action_type=action_type, critical_zones=[0], reasoning="Greedy focus on Zone 0.")
|
| 28 |
+
|
| 29 |
+
if action_type == ActionType.ALLOCATE:
|
| 30 |
+
return FairRecoveryAction(
|
| 31 |
+
action_type=action_type,
|
| 32 |
+
allocations=[ResourceAllocation(zone=0, resource=ResourceType.MEDICAL)],
|
| 33 |
+
reasoning="Maximizing utility in Zone 0."
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
return FairRecoveryAction(action_type=ActionType.EXECUTE)
|
| 37 |
|
| 38 |
def fairness_aware_policy(obs: FairRecoveryObservation) -> FairRecoveryAction:
|
| 39 |
+
"""Fairness-Aware: Prioritizes Zone 4 (highest vulnerability)."""
|
| 40 |
if obs.day > 10: return FairRecoveryAction(action_type=ActionType.SUBMIT)
|
| 41 |
|
| 42 |
+
action_type = _get_phase_action(obs)
|
| 43 |
+
|
| 44 |
+
# Identify most vulnerable zone (usually Zone 4 in hard scenario)
|
| 45 |
+
v_zone = sorted(range(len(obs.zones)), key=lambda i: obs.zones[i].vulnerable_ratio, reverse=True)[0]
|
| 46 |
+
|
| 47 |
+
if action_type == ActionType.ANALYZE:
|
| 48 |
+
return FairRecoveryAction(action_type=action_type, critical_zones=[v_zone], reasoning=f"Prioritizing high-vulnerability Zone {v_zone}.")
|
| 49 |
+
|
| 50 |
+
if action_type == ActionType.ALLOCATE:
|
| 51 |
+
return FairRecoveryAction(
|
| 52 |
+
action_type=action_type,
|
| 53 |
+
allocations=[ResourceAllocation(zone=v_zone, resource=ResourceType.MEDICAL)],
|
| 54 |
+
reasoning=f"Protecting vulnerable population in Zone {v_zone}."
|
| 55 |
+
)
|
| 56 |
|
| 57 |
+
return FairRecoveryAction(action_type=ActionType.EXECUTE)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
server/app.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
"""
|
| 2 |
FairRecovery++ β FastAPI Application.
|
| 3 |
|
| 4 |
-
|
| 5 |
"""
|
| 6 |
|
| 7 |
from __future__ import annotations
|
|
@@ -19,33 +19,6 @@ from fairrecovery_env.models import FairRecoveryAction, FairRecoveryObservation
|
|
| 19 |
from server.fairrecovery_environment import FairRecoveryEnvironment
|
| 20 |
from inference import greedy_policy, fairness_aware_policy
|
| 21 |
|
| 22 |
-
def llm_policy(obs: FairRecoveryObservation):
|
| 23 |
-
"""Real-time LLM inference using HF API."""
|
| 24 |
-
hf_token = os.environ.get("HF_TOKEN")
|
| 25 |
-
if not hf_token:
|
| 26 |
-
return fairness_aware_policy(obs)
|
| 27 |
-
|
| 28 |
-
try:
|
| 29 |
-
zones_str = '\n'.join([f"Zone {z.zone_id}: damage={z.damage:.2f}, vulnerable={z.vulnerable_ratio:.2f}" for z in obs.zones])
|
| 30 |
-
prompt = f"System: You are an AI allocating resources fairly. Respond with JSON action.\nUser: Day {obs.day}. Budget {obs.budget_left}. Zones:\n{zones_str}\nWhat is your next action?"
|
| 31 |
-
|
| 32 |
-
response = requests.post(
|
| 33 |
-
"https://api-inference.huggingface.co/models/meta-llama/Llama-3.2-3B-Instruct",
|
| 34 |
-
headers={"Authorization": f"Bearer {hf_token}"},
|
| 35 |
-
json={"inputs": prompt, "parameters": {"max_new_tokens": 100, "temperature": 0.1}},
|
| 36 |
-
timeout=5
|
| 37 |
-
)
|
| 38 |
-
if response.status_code == 200:
|
| 39 |
-
text = response.json()[0]["generated_text"]
|
| 40 |
-
match = re.search(r'\{.*?\}', text, re.DOTALL)
|
| 41 |
-
if match:
|
| 42 |
-
data = json.loads(match.group())
|
| 43 |
-
return FairRecoveryAction(**data)
|
| 44 |
-
except Exception as e:
|
| 45 |
-
print(f"LLM API failed: {e}")
|
| 46 |
-
|
| 47 |
-
return fairness_aware_policy(obs)
|
| 48 |
-
|
| 49 |
def _build_app():
|
| 50 |
import gradio as gr
|
| 51 |
app = FastAPI(title="FairRecovery++ RL Environment", version="2.0.0")
|
|
@@ -61,13 +34,6 @@ def _build_app():
|
|
| 61 |
action = FairRecoveryAction(**payload)
|
| 62 |
return _env.step(action).model_dump()
|
| 63 |
|
| 64 |
-
# ββ Simulation Logic for Gradio UI βββββββββββββββββββββββββββββββββββββββ
|
| 65 |
-
def translate_zone_status(damage, vulnerability):
|
| 66 |
-
people_affected = int(damage * 10000)
|
| 67 |
-
vulnerable_count = int(people_affected * vulnerability)
|
| 68 |
-
status_icon = "π΄" if damage > 0.6 else "π‘" if damage > 0.3 else "π’"
|
| 69 |
-
return f"{status_icon} **{people_affected:,}** people | β οΈ **{vulnerable_count:,}** vulnerable"
|
| 70 |
-
|
| 71 |
def run_simulation(policy_type: str):
|
| 72 |
env = FairRecoveryEnvironment()
|
| 73 |
obs = env.reset(task_id="multi_disaster_hard")
|
|
@@ -76,31 +42,30 @@ def _build_app():
|
|
| 76 |
logs.append(f"### π¨ SCENARIO: {policy_type.upper()}")
|
| 77 |
|
| 78 |
done = False
|
| 79 |
-
|
| 80 |
-
policy_fn = greedy_policy if policy_type == "Baseline (Greedy)" else llm_policy
|
| 81 |
|
| 82 |
-
while not done
|
| 83 |
action = policy_fn(obs)
|
| 84 |
obs = env.step(action)
|
| 85 |
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
|
|
|
| 90 |
|
| 91 |
done = obs.done
|
| 92 |
-
step_count += 1
|
| 93 |
|
| 94 |
res_eval = "π’ **EQUITY ACHIEVED**" if obs.fairness_score > 0.8 else "π΄ **NEGLECT DETECTED**"
|
| 95 |
-
result_text = f"### π FINAL OUTCOME\n- **Reward:** {obs.cumulative_reward:.3f}\n- **Equity:** {obs.fairness_score:.3f}\n\n{res_eval}"
|
| 96 |
return "\n".join(logs), result_text
|
| 97 |
|
| 98 |
with gr.Blocks(title="FairRecovery++", theme=gr.themes.Soft()) as gradio_app:
|
| 99 |
-
gr.Markdown("# ποΈ FairRecovery++")
|
| 100 |
with gr.Tabs():
|
| 101 |
with gr.Tab("Simulation"):
|
| 102 |
-
policy = gr.Dropdown(choices=["Baseline (Greedy)", "Trained LLM"], value="Baseline (Greedy)")
|
| 103 |
-
btn = gr.Button("Run Simulation")
|
| 104 |
logs = gr.Markdown()
|
| 105 |
results = gr.Markdown()
|
| 106 |
btn.click(run_simulation, inputs=[policy], outputs=[logs, results])
|
|
@@ -114,7 +79,6 @@ def _build_app():
|
|
| 114 |
|
| 115 |
@app.get("/")
|
| 116 |
async def root(): return RedirectResponse(url="/ui/")
|
| 117 |
-
|
| 118 |
return gr.mount_gradio_app(app, gradio_app, path="/ui")
|
| 119 |
|
| 120 |
app = _build_app()
|
|
|
|
| 1 |
"""
|
| 2 |
FairRecovery++ β FastAPI Application.
|
| 3 |
|
| 4 |
+
Updated simulation loop for phase-aware progression.
|
| 5 |
"""
|
| 6 |
|
| 7 |
from __future__ import annotations
|
|
|
|
| 19 |
from server.fairrecovery_environment import FairRecoveryEnvironment
|
| 20 |
from inference import greedy_policy, fairness_aware_policy
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
def _build_app():
|
| 23 |
import gradio as gr
|
| 24 |
app = FastAPI(title="FairRecovery++ RL Environment", version="2.0.0")
|
|
|
|
| 34 |
action = FairRecoveryAction(**payload)
|
| 35 |
return _env.step(action).model_dump()
|
| 36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
def run_simulation(policy_type: str):
|
| 38 |
env = FairRecoveryEnvironment()
|
| 39 |
obs = env.reset(task_id="multi_disaster_hard")
|
|
|
|
| 42 |
logs.append(f"### π¨ SCENARIO: {policy_type.upper()}")
|
| 43 |
|
| 44 |
done = False
|
| 45 |
+
policy_fn = greedy_policy if policy_type == "Baseline (Greedy)" else fairness_aware_policy
|
|
|
|
| 46 |
|
| 47 |
+
while not done:
|
| 48 |
action = policy_fn(obs)
|
| 49 |
obs = env.step(action)
|
| 50 |
|
| 51 |
+
if action.action_type == "execute":
|
| 52 |
+
logs.append(f"β
**Day {obs.day-1} Complete**")
|
| 53 |
+
# Show status of critical zones
|
| 54 |
+
z4 = obs.zones[4]
|
| 55 |
+
logs.append(f" - Zone 4 (Vulnerable) Status: Damage {z4.damage:.2f}, Svc {z4.service_level:.2f}")
|
| 56 |
|
| 57 |
done = obs.done
|
|
|
|
| 58 |
|
| 59 |
res_eval = "π’ **EQUITY ACHIEVED**" if obs.fairness_score > 0.8 else "π΄ **NEGLECT DETECTED**"
|
| 60 |
+
result_text = f"### π FINAL OUTCOME\n- **Reward Score:** {obs.cumulative_reward:.3f}\n- **Equity Index:** {obs.fairness_score:.3f}\n\n{res_eval}"
|
| 61 |
return "\n".join(logs), result_text
|
| 62 |
|
| 63 |
with gr.Blocks(title="FairRecovery++", theme=gr.themes.Soft()) as gradio_app:
|
| 64 |
+
gr.Markdown("# ποΈ FairRecovery++: Disaster Response Simulator")
|
| 65 |
with gr.Tabs():
|
| 66 |
with gr.Tab("Simulation"):
|
| 67 |
+
policy = gr.Dropdown(choices=["Baseline (Greedy)", "Trained LLM (Fairness Aware)"], value="Baseline (Greedy)")
|
| 68 |
+
btn = gr.Button("Run Simulation", variant="primary")
|
| 69 |
logs = gr.Markdown()
|
| 70 |
results = gr.Markdown()
|
| 71 |
btn.click(run_simulation, inputs=[policy], outputs=[logs, results])
|
|
|
|
| 79 |
|
| 80 |
@app.get("/")
|
| 81 |
async def root(): return RedirectResponse(url="/ui/")
|
|
|
|
| 82 |
return gr.mount_gradio_app(app, gradio_app, path="/ui")
|
| 83 |
|
| 84 |
app = _build_app()
|
server/fairrecovery_environment.py
CHANGED
|
@@ -83,6 +83,7 @@ class FairRecoveryEnvironment(Environment):
|
|
| 83 |
typed_action = FairRecoveryAction(action_type=ActionType.NOOP, reasoning=str(e))
|
| 84 |
|
| 85 |
self._state.step_count += 1
|
|
|
|
| 86 |
|
| 87 |
# 1. Update Day Counter
|
| 88 |
# Sequence: Analyze -> Allocate -> Execute -> Day++
|
|
|
|
| 83 |
typed_action = FairRecoveryAction(action_type=ActionType.NOOP, reasoning=str(e))
|
| 84 |
|
| 85 |
self._state.step_count += 1
|
| 86 |
+
self._action_history.append(typed_action.action_type.value)
|
| 87 |
|
| 88 |
# 1. Update Day Counter
|
| 89 |
# Sequence: Analyze -> Allocate -> Execute -> Day++
|