Spaces:
Sleeping
Sleeping
| """ | |
| Smart Personal Task Manager - OpenEnv Environment | |
| Easy: three tasks + list; full 1.0 when at least 2 distinct priorities; else partial credit. | |
| Medium: small +0.07 signals; deadline miss −0.15. | |
| Hard: +0.018 per dep edge on add; rising clean completes; dep-satisfied micro-bonus; topo bonus; | |
| deadline miss −0.15, dependency violation −0.18. | |
| Reward clamp [0.0, 1.0]. | |
| """ | |
| from __future__ import annotations | |
| from datetime import date | |
| from typing import Any, Dict, List, Optional | |
| from uuid import uuid4 | |
| from openenv.core.env_server.interfaces import Environment | |
| from openenv.core.env_server.types import State | |
| try: | |
| from ..models import TaskManagerAction, TaskManagerObservation | |
| except (ModuleNotFoundError, ImportError): | |
| from models import TaskManagerAction, TaskManagerObservation | |
| VALID_PRIORITIES = {"Low", "Normal", "High"} | |
| # Fair penalties (requested ranges) | |
| MEDIUM_DEADLINE_MISS_PENALTY = 0.15 # -0.12 … -0.18 | |
| HARD_DEADLINE_MISS_PENALTY = 0.15 # -0.12 … -0.18 | |
| HARD_DEP_VIOLATION_PENALTY = 0.18 # -0.15 … -0.20 | |
| def _parse_date(s: Optional[str]) -> Optional[date]: | |
| if not s: | |
| return None | |
| try: | |
| return date.fromisoformat(s) | |
| except ValueError: | |
| return None | |
| def _today() -> date: | |
| return date.today() | |
| class OpenenvJayeshEnvironment(Environment): | |
| """Task Manager with Easy / Medium / Hard difficulty levels.""" | |
| SUPPORTS_CONCURRENT_SESSIONS: bool = True | |
| def __init__(self) -> None: | |
| super().__init__() | |
| self._reset_count = 0 | |
| self._scenarios = ["Easy", "Medium", "Hard"] | |
| self._state = State(episode_id=str(uuid4()), step_count=0) | |
| self._init_state() | |
| def _init_state(self) -> None: | |
| self.tasks: List[Dict[str, Any]] = [] | |
| self.difficulty = "Easy" | |
| self.goal_completed = False | |
| self.list_called = False | |
| self.violations: List[str] = [] | |
| self.tasks_added = 0 | |
| self.high_added = 0 | |
| self.high_on_time = 0 | |
| self.clean_completions = 0 | |
| self.deadline_misses = 0 | |
| self.dep_violations = 0 | |
| self._r_add = 0.0 | |
| self._r_priority = 0.0 | |
| self._r_deadline_set = 0.0 | |
| self._r_complete = 0.0 | |
| self._r_hard_dep_ok = 0.0 | |
| self._r_hard_edge = 0.0 | |
| def reset( | |
| self, | |
| seed: Optional[int] = None, | |
| episode_id: Optional[str] = None, | |
| **kwargs: Any, | |
| ) -> TaskManagerObservation: | |
| self._state = State(episode_id=episode_id or str(uuid4()), step_count=0) | |
| self._init_state() | |
| self.difficulty = self._scenarios[self._reset_count % 3] | |
| self._reset_count += 1 | |
| msgs = { | |
| "Easy": ( | |
| "EASY MODE\n" | |
| "Goal: Add 3 tasks with at least 2 different priorities, then call 'list'.\n" | |
| "Full score (1.0) when 3 tasks + list + 2+ priority levels; " | |
| "otherwise partial credit if you list without enough variety." | |
| ), | |
| "Medium": ( | |
| "MEDIUM MODE\n" | |
| "Goal: Add 4 tasks with priorities and deadlines. Every High task on time.\n" | |
| "Rewards: small steps (~+0.07 per signal) for smooth partial credit.\n" | |
| f"Penalty: -{MEDIUM_DEADLINE_MISS_PENALTY:.2f} per deadline miss." | |
| ), | |
| "Hard": ( | |
| "HARD MODE\n" | |
| "Goal: 5 tasks with deps + deadlines; valid order; no misses.\n" | |
| "Rewards: tiny edge credit when linking deps on add; rising clean completes; " | |
| "small bonus when deps satisfied at complete.\n" | |
| f"Penalties: -{HARD_DEP_VIOLATION_PENALTY:.2f} dependency, -{HARD_DEADLINE_MISS_PENALTY:.2f} deadline miss." | |
| ), | |
| } | |
| return TaskManagerObservation( | |
| success=True, | |
| message=msgs[self.difficulty], | |
| tasks=[], | |
| violations=[], | |
| done=False, | |
| reward=0.0, | |
| ) | |
| def step( | |
| self, | |
| action: TaskManagerAction, | |
| timeout_s: Optional[float] = None, | |
| **kwargs: Any, | |
| ) -> TaskManagerObservation: | |
| self._state.step_count += 1 | |
| cmd = (action.command or "").strip().lower() | |
| if cmd == "add": | |
| success, msg = self._add(action) | |
| elif cmd == "complete": | |
| success, msg = self._complete(action) | |
| elif cmd == "list": | |
| self.list_called = True | |
| success, msg = True, self._fmt_list() | |
| else: | |
| success, msg = False, f"Unknown command '{cmd}'. Use: add | complete | list." | |
| reward = self._score() | |
| return TaskManagerObservation( | |
| success=success, | |
| message=msg, | |
| tasks=list(self.tasks), | |
| violations=list(self.violations), | |
| done=self.goal_completed, | |
| reward=reward, | |
| metadata={ | |
| "difficulty": self.difficulty, | |
| "step": self._state.step_count, | |
| "tasks_added": self.tasks_added, | |
| "clean_completions": self.clean_completions, | |
| "dep_violations": self.dep_violations, | |
| "deadline_misses": self.deadline_misses, | |
| }, | |
| ) | |
| def _add(self, action: TaskManagerAction): | |
| title = (action.title or "").strip() | |
| if not title: | |
| return False, "Error: 'title' is required for add." | |
| if any(t["title"] == title for t in self.tasks): | |
| return False, f"Error: task '{title}' already exists." | |
| priority = (action.priority or "Normal").strip() | |
| if priority not in VALID_PRIORITIES: | |
| priority = "Normal" | |
| deadline_str = (action.deadline or "").strip() or None | |
| depends_on: List[str] = [] | |
| for dep in (action.depends_on or []): | |
| dep = dep.strip() | |
| if not dep: | |
| continue | |
| if not any(t["title"] == dep for t in self.tasks): | |
| return False, f"Error: dependency '{dep}' not found. Add it first." | |
| depends_on.append(dep) | |
| task: Dict[str, Any] = { | |
| "title": title, | |
| "priority": priority, | |
| "deadline": deadline_str, | |
| "depends_on": depends_on, | |
| "completed": False, | |
| "deadline_missed": False, | |
| "dep_violation": False, | |
| } | |
| self.tasks.append(task) | |
| self.tasks_added += 1 | |
| if priority == "High": | |
| self.high_added += 1 | |
| if self.difficulty == "Medium": | |
| # Smaller per-signal steps (~0.07) so scores rise gradually (avoid large single-step jumps) | |
| self._r_add += 0.07 | |
| if priority != "Normal": | |
| self._r_priority += 0.07 | |
| if deadline_str: | |
| self._r_deadline_set += 0.07 | |
| elif self.difficulty == "Hard": | |
| # Slightly smaller per-signal steps than Medium to smooth the last adds (e.g. Deploy) | |
| inc = 0.062 | |
| self._r_add += inc | |
| if priority != "Normal": | |
| self._r_priority += inc | |
| if deadline_str: | |
| self._r_deadline_set += inc | |
| if depends_on: | |
| self._r_hard_edge += 0.012 * float(len(depends_on)) | |
| parts = [f"Task '{title}' added (priority={priority}"] | |
| if deadline_str: | |
| parts[0] += f", deadline={deadline_str}" | |
| if depends_on: | |
| parts[0] += f", depends_on={depends_on}" | |
| parts[0] += ")." | |
| return True, " ".join(parts) | |
| def _complete(self, action: TaskManagerAction): | |
| title = (action.title or "").strip() | |
| if not title: | |
| return False, "Error: 'title' is required for complete." | |
| task = next((t for t in self.tasks if t["title"] == title), None) | |
| if task is None: | |
| return False, f"Error: task '{title}' not found." | |
| if task["completed"]: | |
| return False, f"Task '{title}' is already completed." | |
| msgs = [] | |
| dep_viol = False | |
| dl_miss = False | |
| unmet = [ | |
| d for d in task["depends_on"] | |
| if not any(t["title"] == d and t["completed"] for t in self.tasks) | |
| ] | |
| if unmet: | |
| dep_viol = True | |
| task["dep_violation"] = True | |
| self.dep_violations += 1 | |
| v = f"DEP VIOLATION: '{title}' completed before {unmet}." | |
| self.violations.append(v) | |
| msgs.append("(!) Dependency violation — penalty applied.") | |
| dl = _parse_date(task["deadline"]) | |
| if dl and _today() > dl: | |
| dl_miss = True | |
| task["deadline_missed"] = True | |
| self.deadline_misses += 1 | |
| v = f"DEADLINE MISSED: '{title}' was due {task['deadline']} (completed after deadline)." | |
| self.violations.append(v) | |
| msgs.append("(!) Deadline missed — penalty applied.") | |
| if self.difficulty == "Medium": | |
| msgs.append( | |
| f"Score impact (Medium): -{MEDIUM_DEADLINE_MISS_PENALTY:.2f} for this miss " | |
| f"(total deadline-miss penalty: " | |
| f"{self.deadline_misses * MEDIUM_DEADLINE_MISS_PENALTY:.2f})." | |
| ) | |
| elif self.difficulty == "Hard": | |
| msgs.append( | |
| f"Score impact (Hard): -{HARD_DEADLINE_MISS_PENALTY:.2f} for this miss " | |
| f"(total: {self.deadline_misses * HARD_DEADLINE_MISS_PENALTY:.2f})." | |
| ) | |
| task["completed"] = True | |
| if not dep_viol and not dl_miss: | |
| order_idx = self.clean_completions | |
| self.clean_completions += 1 | |
| if task["priority"] == "High": | |
| self.high_on_time += 1 | |
| msgs.append(f"Task '{title}' completed cleanly.") | |
| if self.difficulty == "Medium": | |
| if task["priority"] == "High": | |
| self._r_complete += 0.09 | |
| elif self.difficulty == "Hard": | |
| # Rising reward each clean step; tiny extra when prerequisites were satisfied | |
| step_r = 0.065 + 0.012 * float(order_idx) | |
| self._r_complete += min(0.125, step_r) | |
| if task["depends_on"]: | |
| self._r_hard_dep_ok += 0.025 | |
| else: | |
| msgs.append(f"Task '{title}' completed with violations.") | |
| return True, " ".join(msgs) | |
| def _score(self) -> float: | |
| if self.difficulty == "Easy": | |
| return self._score_easy() | |
| if self.difficulty == "Medium": | |
| return self._score_medium() | |
| return self._score_hard() | |
| def _easy_has_two_priorities(self) -> bool: | |
| if len(self.tasks) < 3: | |
| return False | |
| return len({t["priority"] for t in self.tasks}) >= 2 | |
| def _score_easy(self) -> float: | |
| # Partial: ~+0.11/add, ~+0.12/list. Full 1.0 + done only with 2+ distinct priorities + list. | |
| r = min(3, self.tasks_added) * 0.11 | |
| if self.list_called: | |
| r += 0.12 | |
| if self.tasks_added >= 3 and self.list_called: | |
| if self._easy_has_two_priorities(): | |
| r += 0.55 | |
| self.goal_completed = True | |
| else: | |
| r += 0.40 | |
| return round(min(1.0, r), 3) | |
| def _score_medium(self) -> float: | |
| # Softer caps match smaller increments (4×0.07 ≈ 0.28 per bucket) | |
| r = min(0.28, self._r_add) | |
| r += min(0.28, self._r_priority) | |
| r += min(0.28, self._r_deadline_set) | |
| r += min(0.22, self._r_complete) | |
| r -= self.deadline_misses * MEDIUM_DEADLINE_MISS_PENALTY | |
| high_tasks = [t for t in self.tasks if t["priority"] == "High"] | |
| all_high_on_time = ( | |
| len(high_tasks) >= 1 | |
| and all(t["completed"] and not t["deadline_missed"] for t in high_tasks) | |
| ) | |
| if self.tasks_added >= 4 and all_high_on_time and self.deadline_misses == 0: | |
| self.goal_completed = True | |
| return 1.0 | |
| return round(min(0.94, max(0.0, r)), 3) | |
| def _score_hard(self) -> float: | |
| # Caps aligned with 0.062 increments (~0.31 over 5 adds) to avoid one huge step on task 5 | |
| r = min(0.34, self._r_add) | |
| r += min(0.26, self._r_priority) | |
| r += min(0.26, self._r_deadline_set) | |
| r += min(0.42, self._r_complete) | |
| r += min(0.14, self._r_hard_dep_ok) | |
| r += min(0.08, self._r_hard_edge) | |
| topo_bonus = 0.0 | |
| if self.clean_completions == 5 and self.dep_violations == 0: | |
| topo_bonus = 0.055 | |
| r += topo_bonus | |
| r -= self.dep_violations * HARD_DEP_VIOLATION_PENALTY | |
| r -= self.deadline_misses * HARD_DEADLINE_MISS_PENALTY | |
| all_done = self.tasks_added >= 5 and all(t["completed"] for t in self.tasks) | |
| if all_done and self.dep_violations == 0 and self.deadline_misses == 0: | |
| self.goal_completed = True | |
| return 1.0 | |
| return round(min(0.99, max(0.0, r)), 3) | |
| def _fmt_list(self) -> str: | |
| if not self.tasks: | |
| return "No tasks yet." | |
| lines = [f"Tasks ({len(self.tasks)}):"] | |
| for i, t in enumerate(self.tasks, 1): | |
| status = "DONE" if t["completed"] else "PENDING" | |
| flags = [] | |
| if t.get("deadline_missed"): | |
| flags.append("LATE") | |
| if t.get("dep_violation"): | |
| flags.append("DEP-ERR") | |
| flag_str = f" [{','.join(flags)}]" if flags else "" | |
| dl = f" due={t['deadline']}" if t["deadline"] else "" | |
| dep = f" deps={t['depends_on']}" if t["depends_on"] else "" | |
| lines.append(f" {i}. [{status}]{flag_str} {t['title']} ({t['priority']}{dl}{dep})") | |
| return "\n".join(lines) | |
| def state(self) -> State: | |
| return self._state | |