joshua400
Initial commit: FairRecovery++ complete multi-agent RL environment
ce75dcf
Raw
History Blame Contribute Delete
4.84 kB
"""
FairRecovery β€” Scenario Definitions.
Three scenarios of increasing difficulty. The HARD scenario is the
"fairness trap": a naive utility-maximising agent will always pick
Zone 0 (easy to fix, low vulnerability) and miss Zone 4 (severely
damaged, highest vulnerability). The trained agent must learn to
prioritise vulnerability Γ— damage jointly.
Structure matches OpenEnv reference env (hallucination-detector-gym) tasks.py.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Dict, List
from .constants import Difficulty
@dataclass
class ScenarioConfig:
"""Immutable task configuration for one scenario."""
task_id: str
difficulty: Difficulty
description: str
initial_budget: float
zones: List[Dict]
hint: str = "" # hints shown to the agent in observations
# ──────────────────────────────────────────────────────────────────────────────
# Scenario Registry
# ──────────────────────────────────────────────────────────────────────────────
TASKS: Dict[str, ScenarioConfig] = {
"easy": ScenarioConfig(
task_id = "easy_3zone",
difficulty = Difficulty.EASY,
description= (
"3-zone post-flood scenario. One zone has moderate damage "
"and high vulnerability. Straightforward resource allocation."
),
initial_budget = 80.0,
hint = "Focus on the zone with highest vulnerability Γ— damage.",
zones = [
{"zone_id": 0, "damage": 0.30, "service": 0.70, "vulnerable_ratio": 0.15},
{"zone_id": 1, "damage": 0.80, "service": 0.20, "vulnerable_ratio": 0.85}, # <- critical
{"zone_id": 2, "damage": 0.20, "service": 0.80, "vulnerable_ratio": 0.10},
],
),
"medium": ScenarioConfig(
task_id = "medium_5zone",
difficulty = Difficulty.MEDIUM,
description= (
"5-zone earthquake scenario. Multiple zones damaged. "
"Budget is constrained β€” agent must triage effectively."
),
initial_budget = 60.0,
hint = "Budget is limited. Prioritise zones with high damage AND vulnerability.",
zones = [
{"zone_id": 0, "damage": 0.40, "service": 0.60, "vulnerable_ratio": 0.30},
{"zone_id": 1, "damage": 0.70, "service": 0.30, "vulnerable_ratio": 0.75}, # <- high priority
{"zone_id": 2, "damage": 0.50, "service": 0.50, "vulnerable_ratio": 0.50},
{"zone_id": 3, "damage": 0.65, "service": 0.35, "vulnerable_ratio": 0.65}, # <- high priority
{"zone_id": 4, "damage": 0.15, "service": 0.85, "vulnerable_ratio": 0.20},
],
),
"hard": ScenarioConfig(
task_id = "hard_5zone_fairness_trap",
difficulty = Difficulty.HARD,
description= (
"5-zone hurricane scenario β€” FAIRNESS TRAP. "
"Zone 0 is wealthy (moderate damage, very low vulnerability). "
"Zone 4 is poor (severe damage, extremely high vulnerability). "
"A naive utility-maximiser will fix Zone 0 first (easier ROI). "
"A fairness-aware agent prioritises Zone 4 (higher vulnerability Γ— damage)."
),
initial_budget = 45.0,
hint = (
"FAIRNESS TRAP: Zone 0 looks easy to fix but has very low vulnerability. "
"Zone 4 has highest damage AND highest vulnerability β€” prioritise it."
),
zones = [
# Zone 0: wealthy district β€” moderate damage, very easy to restore, low vulnerability
{"zone_id": 0, "damage": 0.35, "service": 0.65, "vulnerable_ratio": 0.08},
# Zone 1: middle class
{"zone_id": 1, "damage": 0.50, "service": 0.50, "vulnerable_ratio": 0.40},
# Zone 2: mixed
{"zone_id": 2, "damage": 0.60, "service": 0.40, "vulnerable_ratio": 0.55},
# Zone 3: poor district
{"zone_id": 3, "damage": 0.72, "service": 0.28, "vulnerable_ratio": 0.72},
# Zone 4: informal settlement β€” severe damage, highest vulnerability (TRAP zone)
{"zone_id": 4, "damage": 0.92, "service": 0.08, "vulnerable_ratio": 0.96},
],
),
}
def get_task(difficulty: str) -> ScenarioConfig:
"""Retrieve scenario config by difficulty string."""
task = TASKS.get(difficulty)
if task is None:
raise ValueError(
f"Unknown difficulty '{difficulty}'. "
f"Choose from: {list(TASKS.keys())}"
)
return task