def normalise_score(total_reward: float, steps: int, num_districts: int = 2) -> float: """ Fallback score when the /grade endpoint is unreachable. Maps cumulative reward linearly into [0, 1] using task-aware best/worst bounds. """ avg = total_reward / max(steps, 1) worst = num_districts * (-1.5) # -0.5 infection + -1.0 breach per district best = num_districts * (0.5) + 0.3 score = (avg - worst) / (best - worst) return round(min(1.0, max(0.0, score)), 4)