WorkloadBalancingTask

from playwright.sync_api._generated import Page

from .base import CompositionalTask, HumanEvalTask

from ..base import AbstractServiceNowTask
from ..dashboard import WorkLoadBalancingMinMaxRetrievalTask
from ..form import EditProblemTask
from ..knowledge import KnowledgeBaseSearchTask
from ..list import FilterProblemListForWorkLoadBalancingTask
from ..navigation import AllMenuTask
from ..send_chat_message import SendChatMessageGenericTask

from ...api.problem import create_problem
from ...api.report import create_report
from ...api.user import create_user
from ...api.utils import db_delete_from_table, table_api_call
from ...instance import SNowInstance

class WorkloadBalancingTask(CompositionalTask):
    def __init__(
        self,
        seed: int = None,
        instance: SNowInstance = None,
        fixed_config: list[AbstractServiceNowTask] = None,
        level: int = 2,
        min_users: int = 2,
        max_users: int = 4,
        # Ranges to randomly choose from
        max_problem_range: int = [3, 4],
        mid_problem_range: int = [2, 3],
        min_problem_range: int = [1, 2],
    ) -> None:
        """
        Workload balancing task:
        - Navigate to the KB
        - Find the protocol for re-distributing work
        - Find the user who has the greatest number of problems assigned to them
        - Re-assign the problems to the user having the least number of problems assigned to them

        Parameters:
        -----------
        instance: SNowInstance
            The ServiceNow instance to run the task on.
        fixed_config: list[AbstractServiceNowTask]
            A list of subtasks.
        level: int
            The level of the task; choice between 2 and 3. L2 will have all the info in the the goal and start in the SNOW home page.
            L3 will start in a private task page describing the information needed to complete the task and the related company protocol
            to complete it.
        min_users: int
            The minimum number of users to create and to distribute the problems to
        max_users: int
            The maximum number of users to create and to distribute the problems to
        max_problem_range: list[int, int]
            The range of the number of problems to assign to the user with the most problems
        mid_problem_range: list[int, int]
            The range of the number of problems to assign to all users but the ones with the least/most problems
        min_problem_range: list[int, int]
            The range of the number of problems to assign to the user with the least problems
        Attributes:
        -----------
        task_description: str
            The start of the task description to be completed. e.g. "Referring to company protocol 'Agent Workload Balancing', re-distribute the problems with description containing {self.problem_hashtag}"
        short_description: str
            A short description of the task to be completed. e.g. "Balance the workload for problems with description containing {self.problem_hashtag}"
        """
        assert level in [2, 3], "Level must be either 2 or 3"
        self.level = level
        self.protocol_name = "Agent Workload Balancing"
        super().__init__(
            seed=seed,
            instance=instance,
            fixed_config=fixed_config,
            level=level,
            protocol_name=self.protocol_name,
        )

        self.problem_hashtag = (
            f"#PRB{str(id(self) % (10**8)).zfill(9)}"  # identifier to select problems
        )
        self.task_description = None
        self.short_description = None
        self.min_users = min_users
        self.max_users = max_users
        self.max_problem_range = max_problem_range
        self.mid_problem_range = mid_problem_range
        self.min_problem_range = min_problem_range

        # In this case, there will only be 2 users as the values are bound and the top value is excluded in the randint function
        if self.min_users == 2 and self.max_users == 3:
            assert (
                self.min_problem_range[1] <= self.max_problem_range[0]
            ), "The problem ranges should not overlap"
        # In this case, there will be 3 users
        else:
            assert (
                self.min_problem_range[1] <= self.mid_problem_range[0]
                and self.mid_problem_range[1] <= self.max_problem_range[0]
            ), "The problem ranges should not overlap"
        assert self.max_problem_range[1] <= 6, "The maximum number of problems should not exceed 6"

        self.plot_title = None  # The title of the plot created for the report
        self.lowest_priority = 0  # The lowest priority of the problems; a high number indicates a low priority. Set in the setup_goal method

        self.category_name = (
            fake.word() + "-" + fake.word()
        )  # The category of the problems to re-distribute
        self.category_sys_id = (
            None  # The sys_id of the category created for the task; create in the setup_goal method
        )
        self.user_sys_ids = []  # The sys_ids of the users created for the task
        self.problem_sys_ids = []  # The sys_ids of the problems created for the task
        self.report_sys_id = None  # The sys_id of the report created for the task
        self.user_with_most_problems = None  # The name of the user that has the most problems assigned; defined in the setup_goal method
        self.user_with_least_problems = None  # The name of the user that has the least problems assigned; defined in the setup_goal method
        self.problem_to_edit_sys_id = (
            None  # The sys_id of the problem to re-assign; defined in the setup_goal method
        )
        self.problem_to_edit_number = (
            None  # The number of the problem to re-assign; defined in the setup_goal method
        )

    def setup_goal(self, page: Page) -> tuple[str, dict]:
        num_users = self.random.randint(self.min_users, self.max_users)
        max_problems = self.random.randint(*self.max_problem_range)
        min_problems = self.random.randint(*self.min_problem_range)

        # Create users, create problems and assign problems to users
        for i in range(num_users):
            if i == 0:
                num_problems = max_problems
            elif i == num_users - 1:
                num_problems = min_problems
            else:
                num_problems = self.random.randint(*self.mid_problem_range)
            first_name = fake.first_name() + "-" + fake.first_name()
            last_name = fake.last_name() + "-" + fake.last_name()
            user_full_name = first_name + " " + last_name
            _, _, user_sys_id = create_user(
                instance=self.instance,
                first_name=first_name,
                last_name=last_name,
                user_roles=[
                    "admin",
                    "problem_admin",
                    "problem_manager",
                    "problem_coordinator",
                    "problem_task_analyst",
                ],
                random=self.random,
            )
            self.user_sys_ids.append(user_sys_id)

            if i == 0:
                self.user_with_most_problems = user_full_name
            elif i == num_users - 1:
                self.user_with_least_problems = user_full_name

            # Create problems assigned to current user
            for j in range(num_problems):
                # Assign a priority to the problem; 1 being highest priority and 5 being lowest
                # the use of j % 5 is to ensure that the priority is between 1 and 5 and that there is
                # only one problem with the lowest priority
                priority = (j % 5) + 1
                self.lowest_priority = max(self.lowest_priority, priority)
                problem_sys_id, problem_number = create_problem(
                    instance=self.instance,
                    user_sys_id=user_sys_id,
                    priority=priority,
                    problem_hashtag=self.problem_hashtag,
                    return_number=True,
                )
                # The last problem created is the one to re-assign as it will be the one with the lowest priority (highest priority value)
                # and the first user will be the one with the most problems assigned
                if i == 0 and j == num_problems - 1:
                    self.problem_to_edit_sys_id = problem_sys_id
                    self.problem_to_edit_number = problem_number
                self.problem_sys_ids.append(problem_sys_id)

        # Create a report for problems of the current category
        self.report_sys_id, plot_title = create_report(
            instance=self.instance,
            table="problem",
            filter_hashtag=self.problem_hashtag,
            field="assigned_to",
            plot_title=f"Problems for with hashtag {self.problem_hashtag}",
            random=self.random,
        )
        self.plot_title = plot_title

        # Sample a configuration
        config = self._get_config()
        # Get the task description
        self.short_description = (
            f"Balance the workload for problems with hashtag {self.problem_hashtag}"
        )
        self.task_description = f"Referring to company protocol '{self.protocol_name}' (located in the \"Company Protocols\" knowledge base) re-distribute the problems with hashtag={self.problem_hashtag}."

        goal, info = super().setup_goal(page=page, config=config)

        return goal, info

    def _get_config(self) -> list[AbstractServiceNowTask]:
        """ """
        navigate_to_protocol_subtask = [
            # Navigate to the KB
            AllMenuTask(
                instance=self.instance,
                fixed_config={
                    "application": "Self-Service",
                    "module": "Knowledge",
                    "url": "/now/nav/ui/classic/params/target/%24knowledge.do",
                },
                is_validated=False,
                used_in_level_2=False,
            ),
            # Find the protocol for on-boarding a new user
            KnowledgeBaseSearchTask(
                instance=self.instance,
                fixed_config={
                    "alternative_answers": [],
                    "item": f"{self.protocol_name}",
                    "question": f"Can you find the '{self.protocol_name}' Protocol in the Knowledge Base?",
                    "value": "",
                },
                is_validated=False,
                used_in_level_2=False,
            ),
        ]

        find_most_and_least_busy_users_subtask = [
            # Navigate to the reports list
            AllMenuTask(
                instance=self.instance,
                fixed_config={
                    "application": "Reports",
                    "module": "Administration > All",
                    "url": "/now/nav/ui/classic/params/target/sys_report_list.do",
                },
                is_validated=False,
                used_in_level_2=True,
            ),
            WorkLoadBalancingMinMaxRetrievalTask(
                instance=self.instance,
                fixed_config={
                    "url": "/now/nav/ui/classic/params/target/sys_report",
                    "chart_title": self.plot_title,
                    "chart_series": "",
                    "question": "min",
                },
                is_validated=False,
                used_in_level_2=True,
                problem_hashtag=self.problem_hashtag,
            ),
        ]

        reassign_problem_subtask = [
            # Navigate to the hardware asset list
            AllMenuTask(
                instance=self.instance,
                fixed_config={
                    "application": "Problem",
                    "module": "All",
                    "url": "/now/nav/ui/classic/params/target/problem_list.do",
                },
                is_validated=False,
                used_in_level_2=True,
            ),
            # Filter the problems by assignee, and priority = lowest priority
            # The existence of a lower priority problem is guaranteed
            FilterProblemListForWorkLoadBalancingTask(
                instance=self.instance,
                fixed_config={
                    "filter_columns": ["assigned_to", "priority"],
                    "filter_kind": "AND",
                    "filter_values": [
                        f"{self.user_with_most_problems}",
                        f"{self.lowest_priority}",
                    ],
                },
                is_validated=False,
                used_in_level_2=True,
                goal=f'Create a filter to find problems where \n - "Assigned to" is the user with the most problems assigned and \n - "Priority" is "{self.lowest_priority}".',
            ),
            # Assign a problem to the user with the least problems assigned to them
            EditProblemTask(
                instance=self.instance,
                new_values={"assigned_to": f"{self.user_with_least_problems}"},
                record_sys_id=self.problem_to_edit_sys_id,
                record_number=self.problem_to_edit_number,
                is_validated=True,
                used_in_level_2=True,
                level=self.level,
            ),
        ]

        config = (
            navigate_to_protocol_subtask
            + find_most_and_least_busy_users_subtask
            + reassign_problem_subtask
        )

        return config

    def teardown(self) -> None:
        # Delete the users
        for user_sys_id in self.user_sys_ids:
            record_exists = table_api_call(
                instance=self.instance,
                table="sys_user",
                params={"sysparm_query": f"sys_id={user_sys_id}"},
            )
            if record_exists:
                db_delete_from_table(
                    instance=self.instance,
                    table="sys_user",
                    sys_id=user_sys_id,
                )
        # Delete the problems
        for problem_sys_id in self.problem_sys_ids:
            record_exists = table_api_call(
                instance=self.instance,
                table="problem",
                params={"sysparm_query": f"sys_id={problem_sys_id}"},
            )
            if record_exists:
                db_delete_from_table(
                    instance=self.instance,
                    table="problem",
                    sys_id=problem_sys_id,
                )
        # Delete the report
        db_delete_from_table(
            instance=self.instance,
            table="sys_report",
            sys_id=self.report_sys_id,
        )
        super().teardown()

class WorkloadBalancingSmallTask(WorkloadBalancingTask, HumanEvalTask):
    def __init__(self, seed: int = None, instance: SNowInstance = None, level: int = 2) -> None:
        super().__init__(
            seed=seed,
            instance=instance,
            level=level,
            min_users=2,
            max_users=4,
            max_problem_range=[4, 6],
            mid_problem_range=[3, 4],
            min_problem_range=[1, 3],
        )

class WorkloadBalancingMediumTask(WorkloadBalancingTask):
    def __init__(self, seed: int = None, instance: SNowInstance = None, level: int = 2) -> None:
        super().__init__(
            seed=seed,


WorkloadBalancingSmallTask

            record_exists = table_api_call(
                instance=self.instance,
                table="problem",
                params={"sysparm_query": f"sys_id={problem_sys_id}"},
            )
            if record_exists:
                db_delete_from_table(
                    instance=self.instance,
                    table="problem",
                    sys_id=problem_sys_id,
                )
        # Delete the report
        db_delete_from_table(
            instance=self.instance,
            table="sys_report",
            sys_id=self.report_sys_id,
        )
        super().teardown()

class WorkloadBalancingSmallTask(WorkloadBalancingTask, HumanEvalTask):
    def __init__(self, seed: int = None, instance: SNowInstance = None, level: int = 2) -> None:
        super().__init__(
            seed=seed,
            instance=instance,
            level=level,
            min_users=2,
            max_users=4,
            max_problem_range=[4, 6],
            mid_problem_range=[3, 4],
            min_problem_range=[1, 3],
        )

class WorkloadBalancingMediumTask(WorkloadBalancingTask):
    def __init__(self, seed: int = None, instance: SNowInstance = None, level: int = 2) -> None:
        super().__init__(
            seed=seed,
            instance=instance,
            level=level,
            min_users=5,
            max_users=7,
            max_problem_range=[5, 6],
            mid_problem_range=[3, 5],
            min_problem_range=[1, 3],
        )

class WorkloadBalancingLargeTask(WorkloadBalancingTask):
    def __init__(self, seed: int = None, instance: SNowInstance = None, level: int = 2) -> None:
        super().__init__(
            seed=seed,


WorkloadBalancingMediumTask

            table="sys_report",
            sys_id=self.report_sys_id,
        )
        super().teardown()

class WorkloadBalancingSmallTask(WorkloadBalancingTask, HumanEvalTask):
    def __init__(self, seed: int = None, instance: SNowInstance = None, level: int = 2) -> None:
        super().__init__(
            seed=seed,
            instance=instance,
            level=level,
            min_users=2,
            max_users=4,
            max_problem_range=[4, 6],
            mid_problem_range=[3, 4],
            min_problem_range=[1, 3],
        )

class WorkloadBalancingMediumTask(WorkloadBalancingTask):
    def __init__(self, seed: int = None, instance: SNowInstance = None, level: int = 2) -> None:
        super().__init__(
            seed=seed,
            instance=instance,
            level=level,
            min_users=5,
            max_users=7,
            max_problem_range=[5, 6],
            mid_problem_range=[3, 5],
            min_problem_range=[1, 3],
        )

class WorkloadBalancingLargeTask(WorkloadBalancingTask):
    def __init__(self, seed: int = None, instance: SNowInstance = None, level: int = 2) -> None:
        super().__init__(
            seed=seed,
            instance=instance,
            level=level,
            min_users=8,
            max_users=10,
            max_problem_range=[5, 6],
            mid_problem_range=[3, 5],
            min_problem_range=[1, 3],
        )

local_vars = locals().copy()

__TASKS__ = [
    var


WorkloadBalancingLargeTask

            max_problem_range=[4, 6],
            mid_problem_range=[3, 4],
            min_problem_range=[1, 3],
        )

class WorkloadBalancingMediumTask(WorkloadBalancingTask):
    def __init__(self, seed: int = None, instance: SNowInstance = None, level: int = 2) -> None:
        super().__init__(
            seed=seed,
            instance=instance,
            level=level,
            min_users=5,
            max_users=7,
            max_problem_range=[5, 6],
            mid_problem_range=[3, 5],
            min_problem_range=[1, 3],
        )

class WorkloadBalancingLargeTask(WorkloadBalancingTask):
    def __init__(self, seed: int = None, instance: SNowInstance = None, level: int = 2) -> None:
        super().__init__(
            seed=seed,
            instance=instance,
            level=level,
            min_users=8,
            max_users=10,
            max_problem_range=[5, 6],
            mid_problem_range=[3, 5],
            min_problem_range=[1, 3],
        )

local_vars = locals().copy()

__TASKS__ = [
    var
    for var in local_vars.values()
    if isinstance(var, type)
    and issubclass(var, WorkloadBalancingTask)
    and var is not WorkloadBalancingTask
]


            mid_problem_range=[3, 4],
            min_problem_range=[1, 3],
        )

class WorkloadBalancingMediumTask(WorkloadBalancingTask):
    def __init__(self, seed: int = None, instance: SNowInstance = None, level: int = 2) -> None:
        super().__init__(
            seed=seed,
            instance=instance,
            level=level,
            min_users=5,
            max_users=7,
            max_problem_range=[5, 6],
            mid_problem_range=[3, 5],
            min_problem_range=[1, 3],
        )

class WorkloadBalancingLargeTask(WorkloadBalancingTask):
    def __init__(self, seed: int = None, instance: SNowInstance = None, level: int = 2) -> None:
        super().__init__(
            seed=seed,
            instance=instance,
            level=level,
            min_users=8,
            max_users=10,
            max_problem_range=[5, 6],
            mid_problem_range=[3, 5],
            min_problem_range=[1, 3],
        )

local_vars = locals().copy()

__TASKS__ = [
    var
    for var in local_vars.values()
    if isinstance(var, type)
    and issubclass(var, WorkloadBalancingTask)
    and var is not WorkloadBalancingTask
]


        self.lowest_priority = 0  # The lowest priority of the problems; a high number indicates a low priority. Set in the setup_goal method

        self.category_name = (
            fake.word() + "-" + fake.word()
        )  # The category of the problems to re-distribute
        self.category_sys_id = (
            None  # The sys_id of the category created for the task; create in the setup_goal method
        )
        self.user_sys_ids = []  # The sys_ids of the users created for the task
        self.problem_sys_ids = []  # The sys_ids of the problems created for the task
        self.report_sys_id = None  # The sys_id of the report created for the task
        self.user_with_most_problems = None  # The name of the user that has the most problems assigned; defined in the setup_goal method
        self.user_with_least_problems = None  # The name of the user that has the least problems assigned; defined in the setup_goal method
        self.problem_to_edit_sys_id = (
            None  # The sys_id of the problem to re-assign; defined in the setup_goal method
        )
        self.problem_to_edit_number = (
            None  # The number of the problem to re-assign; defined in the setup_goal method
        )

    def setup_goal(self, page: Page) -> tuple[str, dict]:
        num_users = self.random.randint(self.min_users, self.max_users)
        max_problems = self.random.randint(*self.max_problem_range)
        min_problems = self.random.randint(*self.min_problem_range)

        # Create users, create problems and assign problems to users
        for i in range(num_users):
            if i == 0:
                num_problems = max_problems
            elif i == num_users - 1:
                num_problems = min_problems
            else:
                num_problems = self.random.randint(*self.mid_problem_range)
            first_name = fake.first_name() + "-" + fake.first_name()
            last_name = fake.last_name() + "-" + fake.last_name()
            user_full_name = first_name + " " + last_name
            _, _, user_sys_id = create_user(
                instance=self.instance,
                first_name=first_name,
                last_name=last_name,
                user_roles=[
                    "admin",
                    "problem_admin",
                    "problem_manager",
                    "problem_coordinator",
                    "problem_task_analyst",
                ],
                random=self.random,
            )
            self.user_sys_ids.append(user_sys_id)

            if i == 0:
                self.user_with_most_problems = user_full_name
            elif i == num_users - 1:
                self.user_with_least_problems = user_full_name

            # Create problems assigned to current user
            for j in range(num_problems):
                # Assign a priority to the problem; 1 being highest priority and 5 being lowest
                # the use of j % 5 is to ensure that the priority is between 1 and 5 and that there is
                # only one problem with the lowest priority
                priority = (j % 5) + 1
                self.lowest_priority = max(self.lowest_priority, priority)
                problem_sys_id, problem_number = create_problem(
                    instance=self.instance,
                    user_sys_id=user_sys_id,
                    priority=priority,
                    problem_hashtag=self.problem_hashtag,
                    return_number=True,
                )
                # The last problem created is the one to re-assign as it will be the one with the lowest priority (highest priority value)
                # and the first user will be the one with the most problems assigned
                if i == 0 and j == num_problems - 1:
                    self.problem_to_edit_sys_id = problem_sys_id
                    self.problem_to_edit_number = problem_number
                self.problem_sys_ids.append(problem_sys_id)

        # Create a report for problems of the current category
        self.report_sys_id, plot_title = create_report(
            instance=self.instance,
            table="problem",
            filter_hashtag=self.problem_hashtag,
            field="assigned_to",
            plot_title=f"Problems for with hashtag {self.problem_hashtag}",
            random=self.random,
        )
        self.plot_title = plot_title

        # Sample a configuration
        config = self._get_config()
        # Get the task description
        self.short_description = (
            f"Balance the workload for problems with hashtag {self.problem_hashtag}"
        )
        self.task_description = f"Referring to company protocol '{self.protocol_name}' (located in the \"Company Protocols\" knowledge base) re-distribute the problems with hashtag={self.problem_hashtag}."

        goal, info = super().setup_goal(page=page, config=config)

        return goal, info

    def _get_config(self) -> list[AbstractServiceNowTask]:
        """ """
        navigate_to_protocol_subtask = [
            # Navigate to the KB
            AllMenuTask(
                instance=self.instance,
                fixed_config={
                    "application": "Self-Service",
                    "module": "Knowledge",
                    "url": "/now/nav/ui/classic/params/target/%24knowledge.do",
                },
                is_validated=False,
                used_in_level_2=False,
            ),
            # Find the protocol for on-boarding a new user
            KnowledgeBaseSearchTask(
                instance=self.instance,
                fixed_config={
                    "alternative_answers": [],


            table="problem",
            filter_hashtag=self.problem_hashtag,
            field="assigned_to",
            plot_title=f"Problems for with hashtag {self.problem_hashtag}",
            random=self.random,
        )
        self.plot_title = plot_title

        # Sample a configuration
        config = self._get_config()
        # Get the task description
        self.short_description = (
            f"Balance the workload for problems with hashtag {self.problem_hashtag}"
        )
        self.task_description = f"Referring to company protocol '{self.protocol_name}' (located in the \"Company Protocols\" knowledge base) re-distribute the problems with hashtag={self.problem_hashtag}."

        goal, info = super().setup_goal(page=page, config=config)

        return goal, info

    def _get_config(self) -> list[AbstractServiceNowTask]:
        """ """
        navigate_to_protocol_subtask = [
            # Navigate to the KB
            AllMenuTask(
                instance=self.instance,
                fixed_config={
                    "application": "Self-Service",
                    "module": "Knowledge",
                    "url": "/now/nav/ui/classic/params/target/%24knowledge.do",
                },
                is_validated=False,
                used_in_level_2=False,
            ),
            # Find the protocol for on-boarding a new user
            KnowledgeBaseSearchTask(
                instance=self.instance,
                fixed_config={
                    "alternative_answers": [],
                    "item": f"{self.protocol_name}",
                    "question": f"Can you find the '{self.protocol_name}' Protocol in the Knowledge Base?",
                    "value": "",
                },
                is_validated=False,
                used_in_level_2=False,
            ),
        ]

        find_most_and_least_busy_users_subtask = [
            # Navigate to the reports list
            AllMenuTask(
                instance=self.instance,
                fixed_config={
                    "application": "Reports",
                    "module": "Administration > All",
                    "url": "/now/nav/ui/classic/params/target/sys_report_list.do",
                },
                is_validated=False,
                used_in_level_2=True,
            ),
            WorkLoadBalancingMinMaxRetrievalTask(
                instance=self.instance,
                fixed_config={
                    "url": "/now/nav/ui/classic/params/target/sys_report",
                    "chart_title": self.plot_title,
                    "chart_series": "",
                    "question": "min",
                },
                is_validated=False,
                used_in_level_2=True,
                problem_hashtag=self.problem_hashtag,
            ),
        ]

        reassign_problem_subtask = [
            # Navigate to the hardware asset list
            AllMenuTask(
                instance=self.instance,
                fixed_config={
                    "application": "Problem",
                    "module": "All",
                    "url": "/now/nav/ui/classic/params/target/problem_list.do",
                },
                is_validated=False,
                used_in_level_2=True,
            ),
            # Filter the problems by assignee, and priority = lowest priority
            # The existence of a lower priority problem is guaranteed
            FilterProblemListForWorkLoadBalancingTask(
                instance=self.instance,
                fixed_config={
                    "filter_columns": ["assigned_to", "priority"],
                    "filter_kind": "AND",
                    "filter_values": [
                        f"{self.user_with_most_problems}",
                        f"{self.lowest_priority}",
                    ],
                },
                is_validated=False,
                used_in_level_2=True,
                goal=f'Create a filter to find problems where \n - "Assigned to" is the user with the most problems assigned and \n - "Priority" is "{self.lowest_priority}".',
            ),
            # Assign a problem to the user with the least problems assigned to them
            EditProblemTask(
                instance=self.instance,
                new_values={"assigned_to": f"{self.user_with_least_problems}"},
                record_sys_id=self.problem_to_edit_sys_id,
                record_number=self.problem_to_edit_number,
                is_validated=True,
                used_in_level_2=True,
                level=self.level,
            ),
        ]

        config = (
            navigate_to_protocol_subtask
            + find_most_and_least_busy_users_subtask
            + reassign_problem_subtask
        )

        return config

    def teardown(self) -> None:
        # Delete the users
        for user_sys_id in self.user_sys_ids:
            record_exists = table_api_call(
                instance=self.instance,
                table="sys_user",
                params={"sysparm_query": f"sys_id={user_sys_id}"},
            )
            if record_exists:
                db_delete_from_table(
                    instance=self.instance,
                    table="sys_user",
                    sys_id=user_sys_id,
                )
        # Delete the problems
        for problem_sys_id in self.problem_sys_ids:
            record_exists = table_api_call(
                instance=self.instance,
                table="problem",


teardown

            # Assign a problem to the user with the least problems assigned to them
            EditProblemTask(
                instance=self.instance,
                new_values={"assigned_to": f"{self.user_with_least_problems}"},
                record_sys_id=self.problem_to_edit_sys_id,
                record_number=self.problem_to_edit_number,
                is_validated=True,
                used_in_level_2=True,
                level=self.level,
            ),
        ]

        config = (
            navigate_to_protocol_subtask
            + find_most_and_least_busy_users_subtask
            + reassign_problem_subtask
        )

        return config

    def teardown(self) -> None:
        # Delete the users
        for user_sys_id in self.user_sys_ids:
            record_exists = table_api_call(
                instance=self.instance,
                table="sys_user",
                params={"sysparm_query": f"sys_id={user_sys_id}"},
            )
            if record_exists:
                db_delete_from_table(
                    instance=self.instance,
                    table="sys_user",
                    sys_id=user_sys_id,
                )
        # Delete the problems
        for problem_sys_id in self.problem_sys_ids:
            record_exists = table_api_call(
                instance=self.instance,
                table="problem",
                params={"sysparm_query": f"sys_id={problem_sys_id}"},
            )
            if record_exists:
                db_delete_from_table(
                    instance=self.instance,
                    table="problem",
                    sys_id=problem_sys_id,
                )
        # Delete the report
        db_delete_from_table(
            instance=self.instance,
            table="sys_report",
            sys_id=self.report_sys_id,
        )
        super().teardown()

class WorkloadBalancingSmallTask(WorkloadBalancingTask, HumanEvalTask):
    def __init__(self, seed: int = None, instance: SNowInstance = None, level: int = 2) -> None:
        super().__init__(
            seed=seed,
            instance=instance,
            level=level,
            min_users=2,
            max_users=4,
            max_problem_range=[4, 6],
            mid_problem_range=[3, 4],
            min_problem_range=[1, 3],
        )

class WorkloadBalancingMediumTask(WorkloadBalancingTask):
    def __init__(self, seed: int = None, instance: SNowInstance = None, level: int = 2) -> None:
        super().__init__(
            seed=seed,


import json

from playwright.sync_api._generated import Page

from .base import CompositionalTask, HumanEvalTask

from ..base import AbstractServiceNowTask
from ..form import CreateUserTask, CreateHardwareAssetTask
from ..knowledge import KnowledgeBaseSearchTask
from ..navigation import AllMenuTask
from ..service_catalog import OrderAppleMacBookPro15Task

from ...instance import SNowInstance
from ...config import CREATE_USER_CONFIG_PATH, CREATE_HARDWARE_CONFIG_PATH

class OnBoardUserTask(CompositionalTask, HumanEvalTask):
    def __init__(
        self,
        seed: int = None,
        instance: SNowInstance = None,
        fixed_config: list[AbstractServiceNowTask] = None,
        level: int = 2,
    ) -> None:
        """
        Create a compositional task with specific subtasks

        Parameters:
        -----------
        instance: SNowInstance
            The ServiceNow instance to run the task on.
        fixed_config: list[AbstractServiceNowTask]
            A list of subtasks.
        level: int
            The level of the task; choice between 2 and 3. L2 will have all the info in the the goal and start in the SNOW home page.
            L3 will start in a private task page describing the information needed to complete the task and the related company protocol
            to complete it.
        Attributes:
        -----------
        task_description: str
            The start of the task description to be completed. e.g. "Referring to company protocol 'Onboarding a new user', onboard user with the following information: \n"
        short_description: str
            A short description of the task to be completed. e.g. "Onboard user John Doe"
        """
        assert level in [2, 3], "Level must be either 2 or 3"
        self.level = level
        self.protocol_name = "Onboarding a new user"
        super().__init__(
            seed=seed,
            instance=instance,
            fixed_config=fixed_config,
            level=level,
            protocol_name=self.protocol_name,
        )

        self.all_user_configs = CreateUserTask.all_configs()
        self.all_hardware_asset_configs = CreateHardwareAssetTask.all_configs()
        self.task_description = None
        self.short_description = None

    def setup_goal(self, page: Page) -> tuple[str, dict]:
        # Sample a configuration
        config = self.fixed_config if self.fixed_config else self._get_config()
        user_name = (
            config[3].fixed_config["template_record"]["first_name"]
            + " "
            + config[3].fixed_config["template_record"]["last_name"]
        )
        # Get the task description
        self.short_description = f"Onboard user {user_name}"
        self.task_description = f'Referring to company protocol "{self.protocol_name}" (located in the "Company Protocols" knowledge base) onboard user with the following information: \n'

        goal, info = super().setup_goal(page=page, config=config)

        return goal, info

    def _get_config(self) -> list[AbstractServiceNowTask]:
        # Sample base configurations; the hardware config will be modified to include the assigned_to field
        user_config = self.random.choice(self.all_user_configs)
        hardware_config = self.random.choice(self.all_hardware_asset_configs)

        # Get the common fields between the user and hardware configurations to adjust the hardware config
        common_fields = [
            field for field in hardware_config["fields"].keys() if field in user_config["fields"]
        ]
        common_task_fields = [
            field for field in hardware_config["task_fields"] if field in user_config["task_fields"]
        ]
        common_template_record_fields = [
            field
            for field in hardware_config["template_record"].keys()
            if field in user_config["template_record"] and "sys" not in field
        ]

        # Drop the common fields as they create synchronization issues
        for field in common_fields + common_task_fields + common_template_record_fields:
            if field in user_config["fields"]:
                user_config["fields"].pop(field)
            if field in hardware_config["fields"]:
                hardware_config["fields"].pop(field)

            if field in user_config["task_fields"]:
                user_config["task_fields"].remove(field)
            if field in hardware_config["task_fields"]:
                hardware_config["task_fields"].remove(field)

            if field in user_config["template_record"]:
                user_config["template_record"].pop(field)
            if field in hardware_config["template_record"]:
                hardware_config["template_record"].pop(field)

        navigate_to_protocol_subtask = [
            # Navigate to the KB
            AllMenuTask(
                instance=self.instance,
                fixed_config={
                    "application": "Self-Service",
                    "module": "Knowledge",
                    "url": "/now/nav/ui/classic/params/target/%24knowledge.do",
                },
                is_validated=False,
                used_in_level_2=False,
            ),
            # Find the protocol for on-boarding a new user
            KnowledgeBaseSearchTask(
                instance=self.instance,
                fixed_config={
                    "alternative_answers": [],
                    "item": f"{self.protocol_name}",
                    "question": f"Can you find the '{self.protocol_name}' Protocol in the Knowledge Base?",
                    "value": "",
                },
                is_validated=False,
                used_in_level_2=False,
            ),
        ]

        create_user_subtask = [
            # Navigate to the user list
            AllMenuTask(
                instance=self.instance,
                fixed_config={
                    "application": "System Security",
                    "module": "Users and Groups > Users",
                    "url": "/now/nav/ui/classic/params/target/sys_user_list.do",
                },
                is_validated=False,
                used_in_level_2=True,
            ),
            # Create a new user
            CreateUserTask(
                instance=self.instance,
                fixed_config=user_config,
                is_validated=True,
                used_in_level_2=True,
            ),
        ]

        order_hardware_subtask = [
            # Navigate to the hardware asset list
            AllMenuTask(
                instance=self.instance,
                fixed_config={
                    "application": "Self-Service",
                    "module": "Service Catalog",
                    "url": "/now/nav/ui/classic/params/target/catalog_home.do",
                },
                is_validated=False,
                used_in_level_2=True,
            ),
            # Order a MacBook Pro 15
            OrderAppleMacBookPro15Task(
                instance=self.instance,
                fixed_config={
                    "configuration": {},
                    "description": "Apple MacBook Pro",
                    "item": "Apple MacBook Pro 15",
                    "quantity": 1,
                },
                is_validated=True,
                used_in_level_2=True,
            ),
        ]
        # The unique name for the user is created once the task is instantiated
        user_full_name = (
            create_user_subtask[1].template_record["first_name"]
            + " "
            + create_user_subtask[1].template_record["last_name"]
        )
        # Set the assigned_to field in the hardware asset configuration to the user's email
        hardware_config["template_record"]["assigned_to"] = user_full_name
        if "assigned_to" not in hardware_config["task_fields"]:
            hardware_config["task_fields"].append("assigned_to")

        create_hardware_subtask = [
            # Navigate to the hardware asset list
            AllMenuTask(
                instance=self.instance,
                fixed_config={
                    "application": "Asset",
                    "module": "Portfolios > Hardware Assets",
                    "url": "/now/nav/ui/classic/params/target/alm_hardware_list.do",
                },
                is_validated=False,
                used_in_level_2=True,
            ),
            # Create a new hardware asset
            CreateHardwareAssetTask(
                instance=self.instance,
                fixed_config=hardware_config,
                is_validated=True,
                used_in_level_2=True,
            ),
        ]

        config = (
            navigate_to_protocol_subtask
            + create_user_subtask
            + order_hardware_subtask
            + create_hardware_subtask
        )

        return config

__TASKS__ = [OnBoardUserTask]


import json

from playwright.sync_api._generated import Page

from .base import CompositionalTask, HumanEvalTask

from ..base import AbstractServiceNowTask
from ..form import CreateUserTask, CreateHardwareAssetTask
from ..knowledge import KnowledgeBaseSearchTask
from ..navigation import AllMenuTask
from ..service_catalog import OrderAppleMacBookPro15Task

from ...instance import SNowInstance
from ...config import CREATE_USER_CONFIG_PATH, CREATE_HARDWARE_CONFIG_PATH

class OnBoardUserTask(CompositionalTask, HumanEvalTask):
    def __init__(
        self,
        seed: int = None,
        instance: SNowInstance = None,
        fixed_config: list[AbstractServiceNowTask] = None,
        level: int = 2,
    ) -> None:
        """
        Create a compositional task with specific subtasks

        Parameters:
        -----------
        instance: SNowInstance
            The ServiceNow instance to run the task on.
        fixed_config: list[AbstractServiceNowTask]
            A list of subtasks.
        level: int
            The level of the task; choice between 2 and 3. L2 will have all the info in the the goal and start in the SNOW home page.
            L3 will start in a private task page describing the information needed to complete the task and the related company protocol
            to complete it.
        Attributes:
        -----------
        task_description: str
            The start of the task description to be completed. e.g. "Referring to company protocol 'Onboarding a new user', onboard user with the following information: \n"
        short_description: str
            A short description of the task to be completed. e.g. "Onboard user John Doe"
        """
        assert level in [2, 3], "Level must be either 2 or 3"
        self.level = level
        self.protocol_name = "Onboarding a new user"
        super().__init__(
            seed=seed,
            instance=instance,
            fixed_config=fixed_config,
            level=level,
            protocol_name=self.protocol_name,
        )

        self.all_user_configs = CreateUserTask.all_configs()
        self.all_hardware_asset_configs = CreateHardwareAssetTask.all_configs()
        self.task_description = None
        self.short_description = None

    def setup_goal(self, page: Page) -> tuple[str, dict]:
        # Sample a configuration
        config = self.fixed_config if self.fixed_config else self._get_config()
        user_name = (
            config[3].fixed_config["template_record"]["first_name"]
            + " "
            + config[3].fixed_config["template_record"]["last_name"]
        )
        # Get the task description
        self.short_description = f"Onboard user {user_name}"
        self.task_description = f'Referring to company protocol "{self.protocol_name}" (located in the "Company Protocols" knowledge base) onboard user with the following information: \n'

        goal, info = super().setup_goal(page=page, config=config)

        return goal, info

    def _get_config(self) -> list[AbstractServiceNowTask]:
        # Sample base configurations; the hardware config will be modified to include the assigned_to field
        user_config = self.random.choice(self.all_user_configs)


            The start of the task description to be completed. e.g. "Referring to company protocol 'Onboarding a new user', onboard user with the following information: \n"
        short_description: str
            A short description of the task to be completed. e.g. "Onboard user John Doe"
        """
        assert level in [2, 3], "Level must be either 2 or 3"
        self.level = level
        self.protocol_name = "Onboarding a new user"
        super().__init__(
            seed=seed,
            instance=instance,
            fixed_config=fixed_config,
            level=level,
            protocol_name=self.protocol_name,
        )

        self.all_user_configs = CreateUserTask.all_configs()
        self.all_hardware_asset_configs = CreateHardwareAssetTask.all_configs()
        self.task_description = None
        self.short_description = None

    def setup_goal(self, page: Page) -> tuple[str, dict]:
        # Sample a configuration
        config = self.fixed_config if self.fixed_config else self._get_config()
        user_name = (
            config[3].fixed_config["template_record"]["first_name"]
            + " "
            + config[3].fixed_config["template_record"]["last_name"]
        )
        # Get the task description
        self.short_description = f"Onboard user {user_name}"
        self.task_description = f'Referring to company protocol "{self.protocol_name}" (located in the "Company Protocols" knowledge base) onboard user with the following information: \n'

        goal, info = super().setup_goal(page=page, config=config)

        return goal, info

    def _get_config(self) -> list[AbstractServiceNowTask]:
        # Sample base configurations; the hardware config will be modified to include the assigned_to field
        user_config = self.random.choice(self.all_user_configs)
        hardware_config = self.random.choice(self.all_hardware_asset_configs)

        # Get the common fields between the user and hardware configurations to adjust the hardware config
        common_fields = [
            field for field in hardware_config["fields"].keys() if field in user_config["fields"]
        ]
        common_task_fields = [
            field for field in hardware_config["task_fields"] if field in user_config["task_fields"]
        ]
        common_template_record_fields = [
            field
            for field in hardware_config["template_record"].keys()
            if field in user_config["template_record"] and "sys" not in field
        ]

        # Drop the common fields as they create synchronization issues


        self.all_hardware_asset_configs = CreateHardwareAssetTask.all_configs()
        self.task_description = None
        self.short_description = None

    def setup_goal(self, page: Page) -> tuple[str, dict]:
        # Sample a configuration
        config = self.fixed_config if self.fixed_config else self._get_config()
        user_name = (
            config[3].fixed_config["template_record"]["first_name"]
            + " "
            + config[3].fixed_config["template_record"]["last_name"]
        )
        # Get the task description
        self.short_description = f"Onboard user {user_name}"
        self.task_description = f'Referring to company protocol "{self.protocol_name}" (located in the "Company Protocols" knowledge base) onboard user with the following information: \n'

        goal, info = super().setup_goal(page=page, config=config)

        return goal, info

    def _get_config(self) -> list[AbstractServiceNowTask]:
        # Sample base configurations; the hardware config will be modified to include the assigned_to field
        user_config = self.random.choice(self.all_user_configs)
        hardware_config = self.random.choice(self.all_hardware_asset_configs)

        # Get the common fields between the user and hardware configurations to adjust the hardware config
        common_fields = [
            field for field in hardware_config["fields"].keys() if field in user_config["fields"]
        ]
        common_task_fields = [
            field for field in hardware_config["task_fields"] if field in user_config["task_fields"]
        ]
        common_template_record_fields = [
            field
            for field in hardware_config["template_record"].keys()
            if field in user_config["template_record"] and "sys" not in field
        ]

        # Drop the common fields as they create synchronization issues
        for field in common_fields + common_task_fields + common_template_record_fields:
            if field in user_config["fields"]:
                user_config["fields"].pop(field)
            if field in hardware_config["fields"]:
                hardware_config["fields"].pop(field)

            if field in user_config["task_fields"]:
                user_config["task_fields"].remove(field)
            if field in hardware_config["task_fields"]:
                hardware_config["task_fields"].remove(field)

            if field in user_config["template_record"]:
                user_config["template_record"].pop(field)
            if field in hardware_config["template_record"]:
                hardware_config["template_record"].pop(field)

        navigate_to_protocol_subtask = [
            # Navigate to the KB
            AllMenuTask(
                instance=self.instance,
                fixed_config={
                    "application": "Self-Service",
                    "module": "Knowledge",
                    "url": "/now/nav/ui/classic/params/target/%24knowledge.do",
                },
                is_validated=False,
                used_in_level_2=False,
            ),
            # Find the protocol for on-boarding a new user
            KnowledgeBaseSearchTask(
                instance=self.instance,
                fixed_config={
                    "alternative_answers": [],
                    "item": f"{self.protocol_name}",
                    "question": f"Can you find the '{self.protocol_name}' Protocol in the Knowledge Base?",
                    "value": "",
                },
                is_validated=False,
                used_in_level_2=False,
            ),
        ]

        create_user_subtask = [
            # Navigate to the user list
            AllMenuTask(
                instance=self.instance,
                fixed_config={
                    "application": "System Security",
                    "module": "Users and Groups > Users",
                    "url": "/now/nav/ui/classic/params/target/sys_user_list.do",
                },
                is_validated=False,
                used_in_level_2=True,
            ),
            # Create a new user
            CreateUserTask(
                instance=self.instance,
                fixed_config=user_config,
                is_validated=True,
                used_in_level_2=True,
            ),
        ]

        order_hardware_subtask = [
            # Navigate to the hardware asset list
            AllMenuTask(
                instance=self.instance,
                fixed_config={
                    "application": "Self-Service",
                    "module": "Service Catalog",
                    "url": "/now/nav/ui/classic/params/target/catalog_home.do",
                },
                is_validated=False,
                used_in_level_2=True,
            ),
            # Order a MacBook Pro 15
            OrderAppleMacBookPro15Task(
                instance=self.instance,
                fixed_config={
                    "configuration": {},
                    "description": "Apple MacBook Pro",
                    "item": "Apple MacBook Pro 15",
                    "quantity": 1,
                },
                is_validated=True,
                used_in_level_2=True,
            ),
        ]
        # The unique name for the user is created once the task is instantiated
        user_full_name = (
            create_user_subtask[1].template_record["first_name"]
            + " "
            + create_user_subtask[1].template_record["last_name"]
        )
        # Set the assigned_to field in the hardware asset configuration to the user's email
        hardware_config["template_record"]["assigned_to"] = user_full_name
        if "assigned_to" not in hardware_config["task_fields"]:
            hardware_config["task_fields"].append("assigned_to")

        create_hardware_subtask = [
            # Navigate to the hardware asset list
            AllMenuTask(
                instance=self.instance,
                fixed_config={
                    "application": "Asset",
                    "module": "Portfolios > Hardware Assets",
                    "url": "/now/nav/ui/classic/params/target/alm_hardware_list.do",
                },
                is_validated=False,
                used_in_level_2=True,
            ),
            # Create a new hardware asset
            CreateHardwareAssetTask(
                instance=self.instance,
                fixed_config=hardware_config,
                is_validated=True,
                used_in_level_2=True,
            ),
        ]

        config = (
            navigate_to_protocol_subtask
            + create_user_subtask
            + order_hardware_subtask
            + create_hardware_subtask
        )

        return config

__TASKS__ = [OnBoardUserTask]


"""
Dashboard retrieval and do action comp tasks
"""

import json
from functools import partial
import random
import numpy as np
from typing import List

from faker import Faker

fake = Faker()

from playwright.sync_api._generated import Page

from .base import CompositionalTask, InfeasibleCompositionalTask, HumanEvalTask
from .utils.infeasible_configs import get_infeasible_service_catalog_config
from ..base import AbstractServiceNowTask
from ..knowledge import KnowledgeBaseSearchTask

from ...api.incident import create_incident
from ...api.report import create_report
from ...api.user import create_user
from ...api.utils import table_api_call, db_delete_from_table
from ...instance import SNowInstance

from browsergym.workarena.tasks.navigation import AllMenuTask
from browsergym.workarena.tasks.service_catalog import META_CONFIGS

class DashboardRetrieveAndDoTask(CompositionalTask, HumanEvalTask):
    def __init__(
        self,
        instance: SNowInstance = None,
        dashboard_class: AbstractServiceNowTask = None,
        seed: int = None,
        fixed_config: list[AbstractServiceNowTask] = None,
        dashboard_config: dict = None,
        level: int = 2,
    ) -> None:
        """
        Generic task to perform a dashboard retrieval and perform a task.
        Parameters:
        -----------
        instance: SNowInstance
            The ServiceNow instance to run the task on.
        fixed_config: list[AbstractServiceNowTask]
            A list of tuples, each containing a subtask
        dashboard_config: dict
            Configuration to use for the dashboard task.
        level: int
            The level of the task; choice between 2 and 3. L2 will have all the info in the the goal and start in the SNOW home page.
            L3 will start in a private task page describing the information needed to complete the task and the related company protocol
            to complete it.
        Attributes:
        -----------
        task_description: str
            The start of the task description to be completed. Provided by the child class.
        short_description: str
            A short description of the task to be completed. Provided by the child class.
        """
        assert level in [2, 3], "Level must be either 2 or 3"
        self.level = level
        super().__init__(
            instance=instance,
            seed=seed,
            fixed_config=fixed_config,
            level=level,
        )
        self.used_in_level_2 = self.level == 2
        self.dashboard_config = dashboard_config
        self.task_description = None
        self.short_description = None
        self.dashboard_class = dashboard_class
        self.protocol_name = "Dashboard Retrieve Information and Perform Task"
        self.description_mapping = {
            "max": self.random.choice(["maximum", "highest", "greatest"]),
            "min": self.random.choice(["minimum", "lowest", "least"]),
            "mean": self.random.choice(["mean", "average"]),
            "median": "median",
            "mode": "mode (most frequent)",
        }

    def create_report(self) -> None:
        """
        Create task relevant dashboard report
        """
        raise NotImplementedError

    def set_compositional_task(self) -> None:
        """
        Create and return the compositional task
        """
        raise NotImplementedError

    def get_compositional_task(self) -> list[AbstractServiceNowTask]:
        """
        Return the compositional task
        """
        return self.compositional_task

    def _get_config(self) -> list[AbstractServiceNowTask]:

        navigate_to_protocol_subtask = [
            # Navigate to the KB
            AllMenuTask(
                instance=self.instance,
                fixed_config={
                    "application": "Self-Service",
                    "module": "Knowledge",
                    "url": "/now/nav/ui/classic/params/target/%24knowledge.do",
                },
                is_validated=False,
                used_in_level_2=False,
            ),
            # Find the protocol for on-boarding a new user
            KnowledgeBaseSearchTask(
                instance=self.instance,
                fixed_config={
                    "alternative_answers": [],
                    "item": f"{self.protocol_name}",
                    "question": f"Can you find the '{self.protocol_name}' Protocol in the Knowledge Base?",
                    "value": "",
                },
                is_validated=False,
                used_in_level_2=False,
            ),
        ]

        dashboard_retrieval_subtask = [
            # Navigate to the reports list
            AllMenuTask(
                instance=self.instance,
                fixed_config={
                    "application": "Reports",
                    "module": "Administration > All",
                    "url": "/now/nav/ui/classic/params/target/sys_report_list.do",
                },
                is_validated=False,
                used_in_level_2=True,
            ),
            # Find the user with the desired config
            self.dashboard_class(
                instance=self.instance,
                seed=None,
                fixed_config=self.dashboard_config,
                is_validated=False,
                used_in_level_2=True,
            ),
        ]

        config = (
            navigate_to_protocol_subtask
            + dashboard_retrieval_subtask
            + self.get_compositional_task()
        )
        return config

    def teardown(self) -> None:
        return super().teardown()

class DashboardRetrieveAndDoInfeasibleTask(InfeasibleCompositionalTask):
    def __init__(
        self,
        instance: SNowInstance = None,
        dashboard_class: AbstractServiceNowTask = None,
        seed: int = None,
        fixed_config: list[AbstractServiceNowTask] = None,
        dashboard_config: dict = None,
        level: int = 2,
    ) -> None:
        """
        Generic task to perform a dashboard retrieval and perform a task.
        Parameters:
        -----------
        instance: SNowInstance
            The ServiceNow instance to run the task on.
        fixed_config: list[AbstractServiceNowTask]
            A list of tuples, each containing a subtask
        dashboard_config: dict
            Configuration to use for the dashboard task.
        level: int
            The level of the task; choice between 2 and 3. L2 will have all the info in the the goal and start in the SNOW home page.
            L3 will start in a private task page describing the information needed to complete the task and the related company protocol
            to complete it.
        Attributes:
        -----------
        task_description: str
            The start of the task description to be completed. Provided by the child class.
        short_description: str
            A short description of the task to be completed. Provided by the child class.
        """
        assert level in [2, 3], "Level must be either 2 or 3"
        self.level = level
        super().__init__(
            instance=instance,
            seed=seed,
            fixed_config=fixed_config,
            level=level,
        )
        self.used_in_level_2 = self.level == 2
        self.dashboard_config = dashboard_config
        self.task_description = None
        self.short_description = None
        self.dashboard_class = dashboard_class
        self.protocol_name = "Dashboard Retrieve Information and Perform Task"
        self.description_mapping = {
            "max": self.random.choice(["maximum", "highest", "most"]),
            "min": self.random.choice(["minimum", "lowest", "least"]),
            "mean": self.random.choice(["mean", "average"]),
            "median": "median",
            "mode": "mode (most frequent)",
        }

    def create_report(self) -> None:
        """
        Create task relevant dashboard report
        """
        raise NotImplementedError

    def set_compositional_task(self) -> None:
        """
        Create and return the compositional task
        """
        raise NotImplementedError

    def get_compositional_task(self) -> list[AbstractServiceNowTask]:
        """
        Return the compositional task
        """
        return self.compositional_task

    def _get_config(self) -> list[AbstractServiceNowTask]:

        navigate_to_protocol_subtask = [
            # Navigate to the KB
            AllMenuTask(
                instance=self.instance,
                fixed_config={
                    "application": "Self-Service",
                    "module": "Knowledge",
                    "url": "/now/nav/ui/classic/params/target/%24knowledge.do",
                },
                is_validated=False,
                used_in_level_2=False,
                has_description=False,
            ),
            # Find the protocol for on-boarding a new user
            KnowledgeBaseSearchTask(
                instance=self.instance,
                fixed_config={
                    "alternative_answers": [],
                    "item": f"{self.protocol_name}",
                    "question": f"Can you find the '{self.protocol_name}' Protocol in the Knowledge Base?",
                    "value": "",
                },
                is_validated=False,
                used_in_level_2=False,
                has_description=False,
            ),
        ]

        dashboard_retrieval_subtask = [
            # Navigate to the reports list
            AllMenuTask(
                instance=self.instance,
                fixed_config={
                    "application": "Reports",
                    "module": "Administration > All",
                    "url": "/now/nav/ui/classic/params/target/sys_report_list.do",
                },
                is_validated=False,
                used_in_level_2=True,
                has_description=False,
            ),
            # Find the user with the desired config
            self.dashboard_class(
                instance=self.instance,
                seed=None,
                fixed_config=self.dashboard_config,
                is_validated=False,
                used_in_level_2=True,
            ),
        ]

        config = (
            navigate_to_protocol_subtask
            + dashboard_retrieval_subtask
            + self.get_compositional_task()
        )
        return config

    def teardown(self) -> None:
        return super().teardown()

class DashboardRetrieveIncidentAndDoTask(DashboardRetrieveAndDoTask):
    def __init__(
        self,
        instance: SNowInstance = None,
        seed: int = None,
        fixed_config: list[AbstractServiceNowTask] = None,
        level: int = 2,
        max_incidents_per_agent: int = 4,
        min_incidents_per_agent: int = 1,
        num_agents: int = 4,
        question: str = "",
        dashboard_class: AbstractServiceNowTask = None,
    ) -> None:
        """
        Retrieve information based on incidents from the dashboard and do the task.
        """
        self.incident_hashtag = (
            f"#INC{str(id(self) % (10**8)).zfill(9)}"  # identifier to select problems
        )
        self.chart_title = f"Incidents with hashtag {self.incident_hashtag}"
        super().__init__(
            instance=instance,
            seed=seed,
            fixed_config=fixed_config,
            dashboard_config={
                "url": "/now/nav/ui/classic/params/target/sys_report",
                "chart_title": self.chart_title,
                "question": question,
                "chart_series": "",
            },
            level=level,
            dashboard_class=dashboard_class,
        )
        self.question = question
        self.max_incidents_per_agent = max_incidents_per_agent
        self.min_incidents_per_agent = min_incidents_per_agent
        self.num_agents = num_agents
        if (self.max_incidents_per_agent - self.min_incidents_per_agent) < 2 or self.num_agents < 2:
            raise Exception(
                "The difference between maximum incidents and minimum incidents should be at least two. The number of agents should also be at least 2."
            )
        self.task_description = f"You have to retrieve some information from a dashboard chart based on the description below. The chart presents the number of 'incidents' assigned to different agents. After retrieving the information, you will be asked to use it to complete a task.\n \n"
        self.task_description += f"Title of the report: {self.incident_hashtag}\n\n"
        if self.level == 3:
            self.task_description += f"Referring to the company protocol '{self.protocol_name}' (located in the 'Company Protocols' knowledge base), complete the dashboard retrieval task.\n\n"
        self.short_description = (
            f"Retrieve information from the chart with title {self.incident_hashtag} and perform the mentioned task."
            + "\n For calculations, please round off to the next highest integer if required. If the required calculation has multiple possible answers (for example, 'mode' or 'most frequently' occuring value), please consider the highest value.\n\n"
        )

    def create_report(
        self,
        user_roles=["itil"],
    ) -> None:
        self.agents = {}
        self.agent_sysids = []
        for _ in range(self.num_agents):
            agent_response = create_user(
                instance=self.instance,
                first_name=f"{fake.first_name()}-{fake.first_name()}",
                last_name=f"{fake.last_name()}-{fake.last_name()}",
                return_full_response=True,
                user_roles=user_roles,
            )
            self.agents[agent_response["sys_id"]] = agent_response
            self.agent_sysids.append(agent_response["sys_id"])

        highest_agent = self.agent_sysids[
            -1
        ]  # Choose last agent as the agent with maximum incidents
        self.agents[highest_agent]["num_incidents"] = self.max_incidents_per_agent
        self.agents[highest_agent]["incident_configs"] = []

        lowest_agent = self.agent_sysids[
            0
        ]  # Choose first agent as the agent with minimum incidents
        self.agents[lowest_agent]["num_incidents"] = self.min_incidents_per_agent
        self.agents[lowest_agent]["incident_configs"] = []

        for agent_sysid in self.agent_sysids[1:-1]:
            self.agents[agent_sysid]["num_incidents"] = self.random.randint(
                self.min_incidents_per_agent + 1, self.max_incidents_per_agent - 1
            )
            self.agents[agent_sysid]["incident_configs"] = []

        number_assignments = sum([agent["num_incidents"] for agent in self.agents.values()])

        all_existing_incidents = table_api_call(
            instance=self.instance, table="incident", method="GET"
        )["result"]
        self.all_incident_numbers = [incident["number"] for incident in all_existing_incidents]

        self.new_incident_numbers = []
        for _ in range(number_assignments):
            incident_number = (
                self.prefix
                + str(id(self) % (10**8)).zfill(8)[:4]
                + str(self.random.randint(1000, 9999))
            )
            while (
                incident_number in self.all_incident_numbers
                or incident_number in self.new_incident_numbers
            ):
                incident_number = (
                    self.prefix
                    + str(id(self) % (10**8)).zfill(8)[:4]
                    + str(random.randint(1000, 9999))
                )
            self.new_incident_numbers.append(incident_number)

        incident_number_idx = 0
        for agent, agent_attributes in self.agents.items():
            for _ in range(agent_attributes["num_incidents"]):
                incident_response = create_incident(
                    instance=self.instance,
                    incident_number=self.new_incident_numbers[incident_number_idx],
                    caller_sys_id=self._base_user_sysid,
                    category="software",
                    priority=4,
                    impact=2,  # priority is calculated as some combination of impact and urgency
                    urgency=3,
                    incident_hastag=self.incident_hashtag,
                    assigned_to=agent,
                )
                self.agents[agent]["incident_configs"].append(incident_response)
                incident_number_idx += 1

        self.report_sys_id, _ = create_report(
            instance=self.instance,
            table="incident",
            filter_hashtag=self.incident_hashtag,
            field="assigned_to",
            plot_title=self.chart_title,
            random=self.random,
        )

    def get_agent_values(self, attribute_name, filter_than) -> list[str]:
        agent_values = []
        agent_value_sysids = []
        agent_incidents = [
            agent_attributes["num_incidents"] for agent_attributes in self.agents.values()
        ]

        if self.question == "max":
            agent_value_sysids.append(self.agents[self.agent_sysids[-1]]["sys_id"])
            if attribute_name == "assigned_to":
                agent_full_name = (
                    self.agents[self.agent_sysids[-1]]["first_name"]
                    + " "
                    + self.agents[self.agent_sysids[-1]]["last_name"]
                )
                agent_values.append(agent_full_name)
            elif attribute_name == "first_name":
                agent_first_name = self.agents[self.agent_sysids[-1]]["first_name"]
                agent_values.append(agent_first_name)
            else:
                raise Exception("Filter column not supported.")
        elif self.question == "min":
            agent_value_sysids.append(self.agents[self.agent_sysids[0]]["sys_id"])
            if attribute_name == "assigned_to":
                agent_full_name = (
                    self.agents[self.agent_sysids[0]]["first_name"]
                    + " "
                    + self.agents[self.agent_sysids[0]]["last_name"]
                )
                agent_values.append(agent_full_name)
            elif attribute_name == "first_name":
                agent_first_name = self.agents[self.agent_sysids[0]]["first_name"]
                agent_values.append(agent_first_name)
            else:
                raise Exception("Filter column not supported.")
        elif self.question == "mean" or self.question == "median" or self.question == "mode":
            if self.question == "mean":
                mean_incidents = np.mean(agent_incidents)
                incidents_count = int(np.ceil(mean_incidents))
            elif self.question == "median":
                incidents_count = int(np.ceil(np.median(agent_incidents)))
            elif self.question == "mode":
                # We select the maximum value if there are two or more modes
                frequencies = {}
                for count in agent_incidents:
                    if count not in frequencies:
                        frequencies[count] = 1
                    else:
                        frequencies[count] += 1
                sorted_frequencies = {
                    count: frequency
                    for count, frequency in sorted(
                        frequencies.items(), key=lambda item: item[1], reverse=True
                    )
                }
                max_frequency = list(sorted_frequencies.values())[0]
                max_frequencies = [
                    count
                    for count, frequency in sorted_frequencies.items()
                    if frequency == max_frequency
                ]
                incidents_count = int(max(max_frequencies))

            for agent_sysid, agent_attributes in self.agents.items():
                if (
                    filter_than == "greater"
                    and agent_attribute
# ... truncated ...


DashboardRetrieveAndDoTask

fake = Faker()

from playwright.sync_api._generated import Page

from .base import CompositionalTask, InfeasibleCompositionalTask, HumanEvalTask
from .utils.infeasible_configs import get_infeasible_service_catalog_config
from ..base import AbstractServiceNowTask
from ..knowledge import KnowledgeBaseSearchTask

from ...api.incident import create_incident
from ...api.report import create_report
from ...api.user import create_user
from ...api.utils import table_api_call, db_delete_from_table
from ...instance import SNowInstance

from browsergym.workarena.tasks.navigation import AllMenuTask
from browsergym.workarena.tasks.service_catalog import META_CONFIGS

class DashboardRetrieveAndDoTask(CompositionalTask, HumanEvalTask):
    def __init__(
        self,
        instance: SNowInstance = None,
        dashboard_class: AbstractServiceNowTask = None,
        seed: int = None,
        fixed_config: list[AbstractServiceNowTask] = None,
        dashboard_config: dict = None,
        level: int = 2,
    ) -> None:
        """
        Generic task to perform a dashboard retrieval and perform a task.
        Parameters:
        -----------
        instance: SNowInstance
            The ServiceNow instance to run the task on.
        fixed_config: list[AbstractServiceNowTask]
            A list of tuples, each containing a subtask
        dashboard_config: dict
            Configuration to use for the dashboard task.
        level: int
            The level of the task; choice between 2 and 3. L2 will have all the info in the the goal and start in the SNOW home page.
            L3 will start in a private task page describing the information needed to complete the task and the related company protocol
            to complete it.
        Attributes:
        -----------
        task_description: str
            The start of the task description to be completed. Provided by the child class.
        short_description: str
            A short description of the task to be completed. Provided by the child class.
        """
        assert level in [2, 3], "Level must be either 2 or 3"
        self.level = level
        super().__init__(
            instance=instance,
            seed=seed,
            fixed_config=fixed_config,
            level=level,
        )
        self.used_in_level_2 = self.level == 2
        self.dashboard_config = dashboard_config
        self.task_description = None
        self.short_description = None
        self.dashboard_class = dashboard_class
        self.protocol_name = "Dashboard Retrieve Information and Perform Task"
        self.description_mapping = {
            "max": self.random.choice(["maximum", "highest", "greatest"]),
            "min": self.random.choice(["minimum", "lowest", "least"]),
            "mean": self.random.choice(["mean", "average"]),
            "median": "median",
            "mode": "mode (most frequent)",
        }

    def create_report(self) -> None:
        """
        Create task relevant dashboard report
        """
        raise NotImplementedError

    def set_compositional_task(self) -> None:
        """
        Create and return the compositional task
        """
        raise NotImplementedError

    def get_compositional_task(self) -> list[AbstractServiceNowTask]:
        """
        Return the compositional task
        """
        return self.compositional_task

    def _get_config(self) -> list[AbstractServiceNowTask]:

        navigate_to_protocol_subtask = [
            # Navigate to the KB
            AllMenuTask(
                instance=self.instance,
                fixed_config={
                    "application": "Self-Service",
                    "module": "Knowledge",
                    "url": "/now/nav/ui/classic/params/target/%24knowledge.do",
                },
                is_validated=False,
                used_in_level_2=False,
            ),
            # Find the protocol for on-boarding a new user
            KnowledgeBaseSearchTask(
                instance=self.instance,
                fixed_config={
                    "alternative_answers": [],
                    "item": f"{self.protocol_name}",
                    "question": f"Can you find the '{self.protocol_name}' Protocol in the Knowledge Base?",
                    "value": "",
                },
                is_validated=False,
                used_in_level_2=False,
            ),
        ]

        dashboard_retrieval_subtask = [
            # Navigate to the reports list
            AllMenuTask(
                instance=self.instance,
                fixed_config={
                    "application": "Reports",
                    "module": "Administration > All",
                    "url": "/now/nav/ui/classic/params/target/sys_report_list.do",
                },
                is_validated=False,
                used_in_level_2=True,
            ),
            # Find the user with the desired config
            self.dashboard_class(
                instance=self.instance,
                seed=None,
                fixed_config=self.dashboard_config,
                is_validated=False,
                used_in_level_2=True,
            ),
        ]

        config = (
            navigate_to_protocol_subtask
            + dashboard_retrieval_subtask
            + self.get_compositional_task()
        )
        return config

    def teardown(self) -> None:
        return super().teardown()

class DashboardRetrieveAndDoInfeasibleTask(InfeasibleCompositionalTask):
    def __init__(
        self,
        instance: SNowInstance = None,
        dashboard_class: AbstractServiceNowTask = None,
        seed: int = None,
        fixed_config: list[AbstractServiceNowTask] = None,
        dashboard_config: dict = None,
        level: int = 2,
    ) -> None:
        """
        Generic task to perform a dashboard retrieval and perform a task.
        Parameters:
        -----------
        instance: SNowInstance
            The ServiceNow instance to run the task on.
        fixed_config: list[AbstractServiceNowTask]
            A list of tuples, each containing a subtask


DashboardRetrieveAndDoInfeasibleTask

            self.dashboard_class(
                instance=self.instance,
                seed=None,
                fixed_config=self.dashboard_config,
                is_validated=False,
                used_in_level_2=True,
            ),
        ]

        config = (
            navigate_to_protocol_subtask
            + dashboard_retrieval_subtask
            + self.get_compositional_task()
        )
        return config

    def teardown(self) -> None:
        return super().teardown()

class DashboardRetrieveAndDoInfeasibleTask(InfeasibleCompositionalTask):
    def __init__(
        self,
        instance: SNowInstance = None,
        dashboard_class: AbstractServiceNowTask = None,
        seed: int = None,
        fixed_config: list[AbstractServiceNowTask] = None,
        dashboard_config: dict = None,
        level: int = 2,
    ) -> None:
        """
        Generic task to perform a dashboard retrieval and perform a task.
        Parameters:
        -----------
        instance: SNowInstance
            The ServiceNow instance to run the task on.
        fixed_config: list[AbstractServiceNowTask]
            A list of tuples, each containing a subtask
        dashboard_config: dict
            Configuration to use for the dashboard task.
        level: int
            The level of the task; choice between 2 and 3. L2 will have all the info in the the goal and start in the SNOW home page.
            L3 will start in a private task page describing the information needed to complete the task and the related company protocol
            to complete it.
        Attributes:
        -----------
        task_description: str
            The start of the task description to be completed. Provided by the child class.
        short_description: str
            A short description of the task to be completed. Provided by the child class.
        """
        assert level in [2, 3], "Level must be either 2 or 3"
        self.level = level
        super().__init__(
            instance=instance,
            seed=seed,
            fixed_config=fixed_config,
            level=level,
        )
        self.used_in_level_2 = self.level == 2
        self.dashboard_config = dashboard_config
        self.task_description = None
        self.short_description = None
        self.dashboard_class = dashboard_class
        self.protocol_name = "Dashboard Retrieve Information and Perform Task"
        self.description_mapping = {
            "max": self.random.choice(["maximum", "highest", "most"]),
            "min": self.random.choice(["minimum", "lowest", "least"]),
            "mean": self.random.choice(["mean", "average"]),
            "median": "median",
            "mode": "mode (most frequent)",
        }

    def create_report(self) -> None:
        """
        Create task relevant dashboard report
        """
        raise NotImplementedError

    def set_compositional_task(self) -> None:
        """
        Create and return the compositional task
        """
        raise NotImplementedError

    def get_compositional_task(self) -> list[AbstractServiceNowTask]:
        """
        Return the compositional task
        """
        return self.compositional_task

    def _get_config(self) -> list[AbstractServiceNowTask]:

        navigate_to_protocol_subtask = [
            # Navigate to the KB
            AllMenuTask(
                instance=self.instance,
                fixed_config={
                    "application": "Self-Service",
                    "module": "Knowledge",
                    "url": "/now/nav/ui/classic/params/target/%24knowledge.do",
                },
                is_validated=False,
                used_in_level_2=False,
                has_description=False,
            ),
            # Find the protocol for on-boarding a new user
            KnowledgeBaseSearchTask(
                instance=self.instance,
                fixed_config={
                    "alternative_answers": [],
                    "item": f"{self.protocol_name}",
                    "question": f"Can you find the '{self.protocol_name}' Protocol in the Knowledge Base?",
                    "value": "",
                },
                is_validated=False,
                used_in_level_2=False,
                has_description=False,
            ),
        ]

        dashboard_retrieval_subtask = [
            # Navigate to the reports list
            AllMenuTask(
                instance=self.instance,
                fixed_config={
                    "application": "Reports",
                    "module": "Administration > All",
                    "url": "/now/nav/ui/classic/params/target/sys_report_list.do",
                },
                is_validated=False,
                used_in_level_2=True,
                has_description=False,
            ),
            # Find the user with the desired config
            self.dashboard_class(
                instance=self.instance,
                seed=None,
                fixed_config=self.dashboard_config,
                is_validated=False,
                used_in_level_2=True,
            ),
        ]

        config = (
            navigate_to_protocol_subtask
            + dashboard_retrieval_subtask
            + self.get_compositional_task()
        )
        return config

    def teardown(self) -> None:
        return super().teardown()

class DashboardRetrieveIncidentAndDoTask(DashboardRetrieveAndDoTask):
    def __init__(
        self,
        instance: SNowInstance = None,
        seed: int = None,
        fixed_config: list[AbstractServiceNowTask] = None,
        level: int = 2,
        max_incidents_per_agent: int = 4,
        min_incidents_per_agent: int = 1,
        num_agents: int = 4,
        question: str = "",
        dashboard_class: AbstractServiceNowTask = None,
    ) -> None:
        """
        Retrieve information based on incidents from the dashboard and do the task.
        """
        self.incident_hashtag = (
            f"#INC{str(id(self) % (10**8)).zfill(9)}"  # identifier to select problems