| import random |
| import uuid |
| from typing import List, Optional |
| import sys |
| import os |
| sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) |
|
|
| from models import ( |
| DistrictObservation, |
| DistrictTruth, |
| CityObservation, |
| CityState, |
| ) |
| from server.constants import ( |
| SPREAD_RATE_MIN, |
| SPREAD_RATE_MAX, |
| GROWTH_HINT_NOISE, |
| INFECTION_THRESHOLD, |
| SAFE_THRESHOLD, |
| HOSPITAL_BREACH_POINT, |
| NATURAL_RECOVERY_RATE, |
| ) |
|
|
|
|
| def generate_districts( |
| num_districts: int, |
| seed_infections: List[float], |
| ) -> List[DistrictTruth]: |
| assert len(seed_infections) == num_districts |
| raw_densities = [random.uniform(0.5, 1.5) for _ in range(num_districts)] |
| total = sum(raw_densities) |
| densities = [round(d / total, 4) for d in raw_densities] |
| districts = [] |
| for i in range(num_districts): |
| districts.append(DistrictTruth( |
| district_id = i, |
| true_infection_rate = seed_infections[i], |
| true_spread_rate = round(random.uniform(SPREAD_RATE_MIN, SPREAD_RATE_MAX), 4), |
| hospital_capacity_remaining = 1.0, |
| population_density = densities[i], |
| days_since_tested = 99, |
| restriction_active = False, |
| deployed_resources = 0, |
| )) |
| return districts |
|
|
|
|
| def generate_episode_id() -> str: |
| return str(uuid.uuid4())[:8] |
|
|
|
|
| def build_observation( |
| state: CityState, |
| step_count: int, |
| reward: Optional[float] = None, |
| message: Optional[str] = None, |
| done: bool = False, |
| ) -> CityObservation: |
| """ |
| Build the agent-visible observation from hidden city state. |
| Hard task enforces a 3-day lag on reported infection rates. |
| Hospital capacity and growth hints are always real-time. |
| """ |
| district_observations = [] |
| for i, district in enumerate(state.districts): |
| if state.data_lag_days > 0 and len(state.infection_history) >= state.data_lag_days: |
| reported_rate = state.infection_history[-state.data_lag_days][i] |
| else: |
| reported_rate = district.true_infection_rate |
|
|
| noise = random.uniform(-GROWTH_HINT_NOISE, GROWTH_HINT_NOISE) |
| growth_hint = round(max(0.0, min(1.0, district.true_spread_rate + noise)), 4) |
|
|
| district_observations.append(DistrictObservation( |
| district_id = district.district_id, |
| reported_infection_rate = round(reported_rate, 4), |
| growth_rate_hint = growth_hint, |
| hospital_capacity_remaining = round(district.hospital_capacity_remaining, 4), |
| population_density = district.population_density, |
| tested_recently = district.days_since_tested <= 2, |
| restriction_active = district.restriction_active, |
| )) |
|
|
| return CityObservation( |
| districts = district_observations, |
| available_resources = state.available_resources, |
| current_step = step_count, |
| max_steps = state.max_steps, |
| data_lag_days = state.data_lag_days, |
| done = done, |
| reward = reward, |
| message = message, |
| ) |
|
|
|
|
| def compute_spread(districts: List[DistrictTruth]) -> List[float]: |
| """ |
| Advance infection rates by one day using a simplified SIR-inspired model. |
| |
| Net change per district: |
| delta = effective_spread_rate - natural_recovery + geographic_spillover |
| |
| Spillover is linear (no wrap-around). District 0 and the last district |
| are not adjacent, which mirrors a city corridor layout rather than a ring. |
| """ |
| from server.constants import ( |
| ALLOCATE_REDUCTION, |
| RESTRICT_REDUCTION, |
| SPILLOVER_RATE, |
| NATURAL_RECOVERY_RATE, |
| ) |
|
|
| n = len(districts) |
| new_rates = [] |
|
|
| for i, district in enumerate(districts): |
| effective_spread = district.true_spread_rate |
|
|
| if district.restriction_active: |
| effective_spread = max(0.0, effective_spread - RESTRICT_REDUCTION) |
|
|
| if district.deployed_resources > 0: |
| effective_spread = max( |
| 0.0, |
| effective_spread - (ALLOCATE_REDUCTION * district.deployed_resources) |
| ) |
|
|
| net_change = effective_spread - NATURAL_RECOVERY_RATE |
| new_rate = district.true_infection_rate + net_change |
|
|
| if i > 0: |
| new_rate += districts[i - 1].true_infection_rate * SPILLOVER_RATE |
| if i < n - 1: |
| new_rate += districts[i + 1].true_infection_rate * SPILLOVER_RATE |
|
|
| new_rates.append(round(min(1.0, max(0.0, new_rate)), 4)) |
|
|
| return new_rates |
|
|
|
|
| def get_highest_infected_district(districts: List[DistrictTruth]) -> int: |
| return max(districts, key=lambda d: d.true_infection_rate).district_id |
|
|
|
|
| def all_districts_contained(districts: List[DistrictTruth]) -> bool: |
| return all(d.true_infection_rate < SAFE_THRESHOLD for d in districts) |
|
|
|
|
| def any_hospital_breached(districts: List[DistrictTruth]) -> bool: |
| return any(d.hospital_capacity_remaining <= HOSPITAL_BREACH_POINT for d in districts) |
|
|
|
|
| def districts_above_threshold(districts: List[DistrictTruth]) -> List[DistrictTruth]: |
| return [d for d in districts if d.true_infection_rate > INFECTION_THRESHOLD] |
|
|
|
|
| def snapshot_infection_rates(districts: List[DistrictTruth]) -> List[float]: |
| return [d.true_infection_rate for d in sorted(districts, key=lambda d: d.district_id)] |
|
|