Spaces:
Sleeping
Sleeping
| """Task definitions and data loaders for Bug Triage OpenEnv.""" | |
| from __future__ import annotations | |
| import random | |
| from copy import deepcopy | |
| from datetime import datetime | |
| from typing import Optional | |
| from models import TicketGroundTruth, TicketModel | |
| from . import task_easy, task_hard, task_medium | |
| TASK_MODULES = { | |
| task_easy.TASK_ID: task_easy, | |
| task_medium.TASK_ID: task_medium, | |
| task_hard.TASK_ID: task_hard, | |
| } | |
| class TaskDefinition: | |
| """Represents a task with tickets and ground truth.""" | |
| def __init__( | |
| self, | |
| task_id: str, | |
| tickets: list[TicketModel], | |
| ground_truths: list[TicketGroundTruth], | |
| step_budget: int, | |
| available_teams: list[str], | |
| available_components: list[str], | |
| ): | |
| self.task_id = task_id | |
| self.tickets = tickets | |
| self.ground_truths = ground_truths | |
| self.step_budget = step_budget | |
| self.available_teams = available_teams | |
| self.available_components = available_components | |
| self._validate_integrity() | |
| self.truth_map = {gt.ticket_id: gt for gt in ground_truths} | |
| def get_ground_truth(self, ticket_id: str) -> Optional[TicketGroundTruth]: | |
| """Get ground truth for a ticket.""" | |
| return self.truth_map.get(ticket_id) | |
| def _validate_integrity(self) -> None: | |
| """Ensure ticket and ground-truth ids are unique and aligned.""" | |
| ticket_ids = [ticket.ticket_id for ticket in self.tickets] | |
| ground_truth_ids = [truth.ticket_id for truth in self.ground_truths] | |
| if len(ticket_ids) != len(set(ticket_ids)): | |
| raise ValueError("Duplicate ticket_id values found in tickets") | |
| if len(ground_truth_ids) != len(set(ground_truth_ids)): | |
| raise ValueError("Duplicate ticket_id values found in ground truths") | |
| ticket_id_set = set(ticket_ids) | |
| ground_truth_id_set = set(ground_truth_ids) | |
| if ticket_id_set != ground_truth_id_set: | |
| missing_truth = sorted(ticket_id_set - ground_truth_id_set) | |
| extra_truth = sorted(ground_truth_id_set - ticket_id_set) | |
| raise ValueError( | |
| "Tickets and ground truths must contain the same ticket ids. " | |
| f"missing_ground_truth={missing_truth}, extra_ground_truth={extra_truth}" | |
| ) | |
| def shuffle_tickets(self, seed: int): | |
| """Shuffle tickets deterministically.""" | |
| self._validate_integrity() | |
| if not self.tickets: | |
| self.truth_map = {} | |
| return | |
| rng = random.Random(seed) | |
| shuffled_tickets = list(self.tickets) | |
| rng.shuffle(shuffled_tickets) | |
| ground_truth_by_id = {truth.ticket_id: truth for truth in self.ground_truths} | |
| self.tickets = shuffled_tickets | |
| self.ground_truths = [ground_truth_by_id[ticket.ticket_id] for ticket in self.tickets] | |
| self.truth_map = dict(ground_truth_by_id) | |
| def _task_data(task_id: str) -> dict: | |
| module = TASK_MODULES.get(task_id) | |
| if module is None: | |
| raise FileNotFoundError(f"Unknown task id: {task_id}") | |
| return deepcopy(module.TASK_DATA) | |
| def load_task(task_id: str, seed: Optional[int] = None) -> TaskDefinition: | |
| """ | |
| Load a task from in-memory fixture. | |
| Args: | |
| task_id: One of 'bug_triage_easy', 'bug_triage_medium', 'bug_triage_hard' | |
| seed: Random seed for deterministic shuffling | |
| Returns: | |
| TaskDefinition with loaded tickets and ground truth | |
| """ | |
| data = _task_data(task_id) | |
| tickets = [] | |
| for ticket_data in data["tickets"]: | |
| ticket_data["created_at"] = datetime.fromisoformat(ticket_data["created_at"]) | |
| tickets.append(TicketModel(**ticket_data)) | |
| ground_truths = [TicketGroundTruth(**gt_data) for gt_data in data["ground_truths"]] | |
| task = TaskDefinition( | |
| task_id=task_id, | |
| tickets=tickets, | |
| ground_truths=ground_truths, | |
| step_budget=data["step_budget"], | |
| available_teams=data["available_teams"], | |
| available_components=data["available_components"], | |
| ) | |
| if seed is not None: | |
| task.shuffle_tickets(seed) | |
| return task | |
| def list_tasks() -> dict: | |
| """List all available tasks.""" | |
| return { | |
| task_id: { | |
| "difficulty": module.DIFFICULTY, | |
| "description": module.DESCRIPTION, | |
| } | |
| for task_id, module in TASK_MODULES.items() | |
| } | |
| __all__ = [ | |
| "TaskDefinition", | |
| "load_task", | |
| "list_tasks", | |
| ] | |