"""12 task scenarios for the Fish Farm environment. Easy (3): Single-concern, short episodes, forgiving thresholds Medium (4): Multi-concern, events, cascading risks Hard (3): Full lifecycle, compound events, multi-objective optimization Extreme (2): Everything at once, frontier-model difficulty Each task dict contains: - initial_conditions: starting state overrides - events: scheduled Event objects - episode_hours: max episode length - reward_weights: per-component weights for reward calculation - grader: name of grading function - description: natural language task briefing for the agent - difficulty: easy/medium/hard/extreme """ from typing import Any, Dict, List from .engine.events import Event def _make_tasks() -> Dict[str, Dict[str, Any]]: return { # ===================================================================== # EASY TASKS — Single concern, learn one control at a time # ===================================================================== "feeding_basics": { "difficulty": "easy", "episode_hours": 7 * 24, "description": ( "You manage a healthy tilapia tank for 7 days. Your goal: feed the fish " "to achieve steady growth without overfeeding. Fish start at 50g, target 55g+. " "Keep FCR below 2.0. Zero fish should die from starvation or overfeeding." ), "initial_conditions": { "weight_g": 50.0, "population": 5000, "temp": 30.0, "DO": 7.0, "TAN": 0.1, "pH": 7.5, "day_of_year": 90, }, "events": [], "reward_weights": {"growth": 0.5, "fcr": 0.3, "survival": 0.2}, "grader": "feeding_grader", "target_weight": 55.0, }, "oxygen_management": { "difficulty": "easy", "episode_hours": 3 * 24, "description": ( "It's a hot week (air temp 35C). Dissolved oxygen is dropping. " "Your job: keep DO above 5.0 mg/L at all times using the aerator. " "Fish are already stocked at moderate density. Score based on " "minimum DO maintained and time spent in safe zone." ), "initial_conditions": { "weight_g": 100.0, "population": 4000, "temp": 32.0, "DO": 6.0, "TAN": 0.3, "pH": 7.5, "day_of_year": 180, "base_air_temp": 35.0, }, "events": [], "reward_weights": {"do_stability": 0.5, "do_risk": 0.2, "efficiency": 0.3}, "grader": "oxygen_grader", }, "water_quality_balance": { "difficulty": "easy", "episode_hours": 7 * 24, "description": ( "Manage all water quality parameters simultaneously: keep DO > 5, " "ammonia (UIA) < 0.05, pH 6.5-8.5, temperature 27-32C. " "You have full control: feeding, aeration, heater, water exchange. " "Score = time-averaged water quality composite score." ), "initial_conditions": { "weight_g": 80.0, "population": 5000, "temp": 29.0, "DO": 7.0, "TAN": 0.2, "pH": 7.5, "day_of_year": 100, }, "events": [], "reward_weights": {"water_quality": 0.8, "efficiency": 0.2}, "grader": "water_quality_grader", }, # ===================================================================== # MEDIUM TASKS — Multi-concern, events, cascading risks # ===================================================================== "temperature_stress": { "difficulty": "medium", "episode_hours": 5 * 24, "description": ( "ALERT: A heat wave is hitting. Air temperature will reach 38C for " "3 days starting hour 24. You must manage water temperature using " "heater (cooling mode), reduce feeding (fish eat less in heat), and " "increase aeration (warm water holds less oxygen). " "Goal: Keep fish alive and growing through the crisis." ), "initial_conditions": { "weight_g": 120.0, "population": 8000, "temp": 30.0, "DO": 7.0, "TAN": 0.2, "pH": 7.5, "day_of_year": 200, "base_air_temp": 33.0, }, "events": [ Event(type="heat_wave", trigger_hour=24, severity=0.7, duration_hours=72, description="HEAT WAVE: Air temp reaching 38C"), ], "reward_weights": {"survival": 0.4, "growth": 0.3, "water_quality": 0.3}, "grader": "stress_survival_grader", }, "ammonia_crisis": { "difficulty": "medium", "episode_hours": 3 * 24, "description": ( "EMERGENCY: The biofilter has partially failed (50% capacity). " "Ammonia is rising. You must: reduce feeding immediately, increase " "water exchange, maintain aeration. Goal: prevent ammonia (UIA) from " "reaching lethal levels (>0.6 mg/L) while keeping fish alive." ), "initial_conditions": { "weight_g": 150.0, "population": 7000, "temp": 30.0, "DO": 6.5, "TAN": 1.5, "pH": 7.8, "day_of_year": 150, }, "events": [ Event(type="equipment_failure", trigger_hour=0, severity=0.5, duration_hours=48, description="BIOFILTER FAILURE: 50% capacity", equipment="biofilter"), ], "reward_weights": {"ammonia_control": 0.4, "survival": 0.4, "efficiency": 0.2}, "grader": "ammonia_crisis_grader", }, "disease_outbreak": { "difficulty": "medium", "episode_hours": 10 * 24, "description": ( "Fish are showing signs of stress — sluggish feeding, elevated mortality. " "A disease may be developing. Watch for: increasing mortality, " "feeding refusal, and behavioral changes. If you detect disease, " "apply treatment ('antibiotics'). Also manage water quality to " "reduce stress. Goal: contain the outbreak with <10% total mortality." ), "initial_conditions": { "weight_g": 200.0, "population": 6000, "temp": 31.0, "DO": 6.0, "TAN": 0.5, "pH": 7.6, "day_of_year": 120, }, "events": [ Event(type="disease", trigger_hour=12, severity=0.4, duration_hours=0, description="Disease pathogen introduced"), ], "reward_weights": {"survival": 0.4, "treatment_timing": 0.3, "water_quality": 0.3}, "grader": "disease_grader", }, "growth_optimization": { "difficulty": "medium", "episode_hours": 14 * 24, "description": ( "Optimize fish growth over 2 weeks. Fish start at 80g, target 120g+. " "Balance aggressive feeding (faster growth) against water quality " "degradation (ammonia, DO). Achieve the best FCR possible while " "maximizing weight gain. Minimize mortality." ), "initial_conditions": { "weight_g": 80.0, "population": 6000, "temp": 30.0, "DO": 7.0, "TAN": 0.1, "pH": 7.5, "day_of_year": 90, }, "events": [], "reward_weights": {"growth": 0.4, "fcr": 0.3, "survival": 0.2, "water_quality": 0.1}, "grader": "growth_optimization_grader", "target_weight": 120.0, }, # ===================================================================== # HARD TASKS — Full lifecycle, compound events, multi-objective # ===================================================================== "full_growout": { "difficulty": "hard", "episode_hours": 60 * 24, "description": ( "Complete grow-out cycle: take fish from 20g fingerlings to market " "weight (400g+). Manage all systems over 60 days. Random weather, " "possible disease, possible equipment issues. Score on: final weight, " "survival rate, FCR, and profit. Decide when to harvest for max value." ), "initial_conditions": { "weight_g": 20.0, "population": 7000, "temp": 28.0, "DO": 7.5, "TAN": 0.05, "pH": 7.5, "day_of_year": 60, }, "events": [], "reward_weights": {"profit": 0.3, "growth": 0.25, "survival": 0.25, "fcr": 0.1, "water_quality": 0.1}, "grader": "full_growout_grader", "target_weight": 400.0, }, "storm_response": { "difficulty": "hard", "episode_hours": 5 * 24, "description": ( "SEVERE STORM WARNING: A major storm hits at hour 24. Effects: " "temperature drops 8C, power outage for 12 hours (aerators, heater, " "biofilter ALL down), high winds. After power returns, biofilter " "needs 24 hours to recover. Your job: maximize survival through the crisis. " "Pre-position your systems before the storm hits." ), "initial_conditions": { "weight_g": 200.0, "population": 8000, "temp": 30.0, "DO": 7.5, "TAN": 0.2, "pH": 7.5, "day_of_year": 180, }, "events": [ Event(type="storm", trigger_hour=24, severity=0.9, duration_hours=48, description="SEVERE STORM: Temp -8C, high winds"), Event(type="power_outage", trigger_hour=24, severity=1.0, duration_hours=12, description="POWER OUTAGE: All equipment offline"), Event(type="equipment_failure", trigger_hour=36, severity=0.7, duration_hours=24, description="BIOFILTER RECOVERY: Reduced capacity post-storm", equipment="biofilter"), ], "reward_weights": {"survival": 0.6, "water_quality": 0.3, "efficiency": 0.1}, "grader": "storm_grader", }, "multi_objective": { "difficulty": "hard", "episode_hours": 30 * 24, "description": ( "Multi-objective challenge: simultaneously maximize profit, maintain " "fish welfare (stress < 0.3), and minimize environmental impact " "(water discharge quality). Score is the product of all three objectives — " "neglecting any one dimension tanks your score. " "Fish start at 100g in a moderately stocked tank." ), "initial_conditions": { "weight_g": 100.0, "population": 8000, "temp": 29.0, "DO": 7.0, "TAN": 0.2, "pH": 7.5, "day_of_year": 100, }, "events": [], "reward_weights": {"profit": 0.33, "welfare": 0.34, "environment": 0.33}, "grader": "multi_objective_grader", }, # ===================================================================== # EXTREME TASKS — Everything at once, frontier-model difficulty # ===================================================================== "catastrophe_prevention": { "difficulty": "extreme", "episode_hours": 14 * 24, "description": ( "CRITICAL SITUATION: Multiple simultaneous challenges. " "Day 1: Algae bloom developing (DO supersaturation then crash). " "Day 3: Equipment degradation (aerator at 60% capacity). " "Day 5: Disease outbreak detected. " "Day 7: Market price drops 40%. " "Day 10: Feed delivery delayed (inventory running low). " "Prevent mass mortality, optimize harvest timing despite falling prices, " "manage disease while resources are constrained. " "This task separates frontier models from basic agents." ), "initial_conditions": { "weight_g": 250.0, "population": 7000, "temp": 31.0, "DO": 8.0, "TAN": 0.4, "pH": 7.8, "day_of_year": 200, }, "events": [ Event(type="algae_bloom", trigger_hour=12, severity=0.6, duration_hours=48, description="ALGAE BLOOM: DO swinging wildly"), Event(type="equipment_failure", trigger_hour=72, severity=0.4, duration_hours=96, description="AERATOR DEGRADED: 60% capacity", equipment="aerator"), Event(type="disease", trigger_hour=120, severity=0.5, duration_hours=0, description="DISEASE DETECTED: Mortality rising"), Event(type="price_change", trigger_hour=168, severity=0.4, duration_hours=168, description="MARKET CRASH: Fish price -40%", price_multiplier=0.6), Event(type="feed_shortage", trigger_hour=240, severity=0.7, duration_hours=48, description="FEED DELAYED: Inventory critical"), ], "reward_weights": {"survival": 0.3, "profit": 0.25, "water_quality": 0.2, "disease_control": 0.15, "timing": 0.1}, "grader": "catastrophe_grader", }, "season_management": { "difficulty": "extreme", "episode_hours": 90 * 24, "description": ( "Full 90-day season. Fish from 10g to market weight. " "Seasonal temperature changes (summer peak). Random storms, " "random disease. Feed inventory must be managed (deliveries " "every 14 days). Decide optimal harvest timing — market price " "fluctuates weekly. Score = ROI (profit / total investment). " "The best agents will achieve >50% ROI." ), "initial_conditions": { "weight_g": 10.0, "population": 10000, "temp": 27.0, "DO": 7.5, "TAN": 0.05, "pH": 7.5, "day_of_year": 60, }, "events": [], "reward_weights": {"roi": 0.4, "growth": 0.2, "survival": 0.2, "fcr": 0.1, "welfare": 0.1}, "grader": "season_grader", }, } TASKS = _make_tasks() def get_task(task_id: str) -> Dict[str, Any]: if task_id not in TASKS: raise ValueError(f"Unknown task_id: {task_id}. Available: {list(TASKS.keys())}") return TASKS[task_id] def list_all_tasks() -> List[Dict[str, str]]: return [ { "task_id": tid, "difficulty": task["difficulty"], "description": task["description"], "episode_hours": task["episode_hours"], } for tid, task in TASKS.items() ]