    FilterExpenseLinesAndDeleteWrongInvestments, HumanEvalTask
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[3, 5],
            budget=150000,
            mode="random",
            level=level,
        )

class FilterRandomExpensesAndDeleteWrongInvestmentsMediumTask(
    FilterExpenseLinesAndDeleteWrongInvestments
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[6, 8],
            budget=150000,
            mode="random",
            level=level,
        )

class FilterRandomExpensesAndDeleteWrongInvestmentsLargeTask(
    FilterExpenseLinesAndDeleteWrongInvestments
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[9, 12],
            budget=150000,
            mode="random",
            level=level,


    FilterExpenseLinesAndDeleteWrongInvestments
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[6, 8],
            budget=150000,
            mode="random",
            level=level,
        )

class FilterRandomExpensesAndDeleteWrongInvestmentsLargeTask(
    FilterExpenseLinesAndDeleteWrongInvestments
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[9, 12],
            budget=150000,
            mode="random",
            level=level,
        )

class FilterSingleItemExpensesAndDeleteWrongInvestmentsSmallTask(
    FilterExpenseLinesAndDeleteWrongInvestments, HumanEvalTask
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[3, 5],
            budget=150000,
            mode="single_item",
            level=level,


    FilterExpenseLinesAndDeleteWrongInvestments
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[9, 12],
            budget=150000,
            mode="random",
            level=level,
        )

class FilterSingleItemExpensesAndDeleteWrongInvestmentsSmallTask(
    FilterExpenseLinesAndDeleteWrongInvestments, HumanEvalTask
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[3, 5],
            budget=150000,
            mode="single_item",
            level=level,
        )

class FilterSingleItemExpensesAndDeleteWrongInvestmentsMediumTask(
    FilterExpenseLinesAndDeleteWrongInvestments
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[6, 8],
            budget=150000,
            mode="single_item",
            level=level,


    FilterExpenseLinesAndDeleteWrongInvestments, HumanEvalTask
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[3, 5],
            budget=150000,
            mode="single_item",
            level=level,
        )

class FilterSingleItemExpensesAndDeleteWrongInvestmentsMediumTask(
    FilterExpenseLinesAndDeleteWrongInvestments
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[6, 8],
            budget=150000,
            mode="single_item",
            level=level,
        )

class FilterSingleItemExpensesAndDeleteWrongInvestmentsLargeTask(
    FilterExpenseLinesAndDeleteWrongInvestments
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[9, 12],
            budget=150000,
            mode="single_item",
            level=level,


    FilterExpenseLinesAndDeleteWrongInvestments
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[6, 8],
            budget=150000,
            mode="single_item",
            level=level,
        )

class FilterSingleItemExpensesAndDeleteWrongInvestmentsLargeTask(
    FilterExpenseLinesAndDeleteWrongInvestments
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[9, 12],
            budget=150000,
            mode="single_item",
            level=level,
        )

class FilterSingleItemUniformExpensesAndDeleteWrongInvestmentsSmallTask(
    FilterExpenseLinesAndDeleteWrongInvestments, HumanEvalTask
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[3, 5],
            budget=150000,
            mode="single_item_uniform",
            level=level,


    FilterExpenseLinesAndDeleteWrongInvestments
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[9, 12],
            budget=150000,
            mode="single_item",
            level=level,
        )

class FilterSingleItemUniformExpensesAndDeleteWrongInvestmentsSmallTask(
    FilterExpenseLinesAndDeleteWrongInvestments, HumanEvalTask
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[3, 5],
            budget=150000,
            mode="single_item_uniform",
            level=level,
        )

class FilterSingleItemUniformExpensesAndDeleteWrongInvestmentsMediumTask(
    FilterExpenseLinesAndDeleteWrongInvestments
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[6, 8],
            budget=150000,
            mode="single_item_uniform",
            level=level,


    FilterExpenseLinesAndDeleteWrongInvestments, HumanEvalTask
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[3, 5],
            budget=150000,
            mode="single_item_uniform",
            level=level,
        )

class FilterSingleItemUniformExpensesAndDeleteWrongInvestmentsMediumTask(
    FilterExpenseLinesAndDeleteWrongInvestments
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[6, 8],
            budget=150000,
            mode="single_item_uniform",
            level=level,
        )

class FilterSingleItemUniformExpensesAndDeleteWrongInvestmentsLargeTask(
    FilterExpenseLinesAndDeleteWrongInvestments
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[9, 12],
            budget=150000,
            mode="single_item_uniform",
            level=level,


    FilterExpenseLinesAndDeleteWrongInvestments
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[6, 8],
            budget=150000,
            mode="single_item_uniform",
            level=level,
        )

class FilterSingleItemUniformExpensesAndDeleteWrongInvestmentsLargeTask(
    FilterExpenseLinesAndDeleteWrongInvestments
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[9, 12],
            budget=150000,
            mode="single_item_uniform",
            level=level,
        )

class FilterTwoItemsUniformExpensesAndDeleteWrongInvestmentsSmallTask(
    FilterExpenseLinesAndDeleteWrongInvestments, HumanEvalTask
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[3, 5],
            budget=150000,
            mode="n_items",
            level=level,


    FilterExpenseLinesAndDeleteWrongInvestments
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[9, 12],
            budget=150000,
            mode="single_item_uniform",
            level=level,
        )

class FilterTwoItemsUniformExpensesAndDeleteWrongInvestmentsSmallTask(
    FilterExpenseLinesAndDeleteWrongInvestments, HumanEvalTask
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[3, 5],
            budget=150000,
            mode="n_items",
            level=level,
            num_items_uniform=2,
        )

class FilterThreeItemsUniformExpensesAndDeleteWrongInvestmentsMediumTask(
    FilterExpenseLinesAndDeleteWrongInvestments
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[6, 8],
            budget=150000,
            mode="n_items",
            level=level,


):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[3, 5],
            budget=150000,
            mode="n_items",
            level=level,
            num_items_uniform=2,
        )

class FilterThreeItemsUniformExpensesAndDeleteWrongInvestmentsMediumTask(
    FilterExpenseLinesAndDeleteWrongInvestments
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[6, 8],
            budget=150000,
            mode="n_items",
            level=level,
            num_items_uniform=3,
        )

class FilterThreeItemsUniformExpensesAndDeleteWrongInvestmentsLargeTask(
    FilterExpenseLinesAndDeleteWrongInvestments
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[9, 12],
            budget=150000,
            mode="n_items",
            level=level,


):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[6, 8],
            budget=150000,
            mode="n_items",
            level=level,
            num_items_uniform=3,
        )

class FilterThreeItemsUniformExpensesAndDeleteWrongInvestmentsLargeTask(
    FilterExpenseLinesAndDeleteWrongInvestments
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[9, 12],
            budget=150000,
            mode="n_items",
            level=level,
            num_items_uniform=3,
        )

local_vars = locals().copy()

__TASKS__ = [
    var
    for var in local_vars.values()
    if isinstance(var, type)
    and issubclass(var, FilterAndDoTask)
    and var is not FilterAndDoTask
    and var is not FilterExpensesAndAllocateInvestmentsTask
    and var is not FilterExpensesAndFindTotalReturnTask
    and var is not FilterExpenseLinesAndDeleteWrongInvestments
    and var is not FilterExpensesFindTotalReturnAndSelectInvestmentsTask
    and var is not FilterExpensesAndSelectInvestmentsTask
]


        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[6, 8],
            budget=150000,
            mode="n_items",
            level=level,
            num_items_uniform=3,
        )

class FilterThreeItemsUniformExpensesAndDeleteWrongInvestmentsLargeTask(
    FilterExpenseLinesAndDeleteWrongInvestments
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[9, 12],
            budget=150000,
            mode="n_items",
            level=level,
            num_items_uniform=3,
        )

local_vars = locals().copy()

__TASKS__ = [
    var
    for var in local_vars.values()
    if isinstance(var, type)
    and issubclass(var, FilterAndDoTask)
    and var is not FilterAndDoTask
    and var is not FilterExpensesAndAllocateInvestmentsTask
    and var is not FilterExpensesAndFindTotalReturnTask
    and var is not FilterExpenseLinesAndDeleteWrongInvestments
    and var is not FilterExpensesFindTotalReturnAndSelectInvestmentsTask
    and var is not FilterExpensesAndSelectInvestmentsTask
]


        fixed_config: List[AbstractServiceNowTask] = None,
        num_expenses: List[int] = [3, 4],
        budget: int = 150000,
        mode: str = "random",
        num_items_uniform: int = None,
        level: int = 2,
    ) -> None:
        super().__init__(
            seed=seed,
            instance=instance,
            fixed_config=fixed_config,
            num_expenses=num_expenses,
            budget=budget,
            mode=mode,
            answer_format="cleanup",
            num_items_uniform=num_items_uniform,
            level=level,
        )
        self.task_description = f'Follow protocol "{self.protocol_name}" (located in the "Company Protocols" knowledge base) to allocate investments to the expenses with short description containing {self.expense_hashtag} to maximize returns while fitting inside the budget of {self.budget}$. Delete the investments that were not selected. '

    def _setup_list(self) -> None:
        super()._setup_list()
        # in modes "n_items", "single_item_uniform", this yields one of many valid solutions
        for i, expense_line_number in enumerate(self.incorrect_investments):
            skip_description = i > 0
            expense_line_sys_id = self.expense_line_sys_ids[i]
            self.tasks.append(
                DeleteExpenseLineKnapsack(
                    instance=self.instance,
                    record_number=expense_line_number,
                    record_sys_id=expense_line_sys_id,
                    fixed_config={
                        "field_name": "number",
                        "field_value": f"{expense_line_number}",
                    },
                    used_in_level_2=True,
                    is_validated=False,
                    budget=self.budget,
                    answer_format=self.answer_format,
                    level=self.level,
                    skip_description=skip_description,
                )
            )

    def validate(self, page: Page, chat_messages: List[str]) -> Tuple[float, bool, str, dict]:
        expenses = table_api_call(
            instance=self.instance,
            table="fm_expense_line",
            params={
                "sysparm_query": f"short_descriptionLIKE{self.expense_hashtag}",
                "sysparm_fields": "number,amount,sys_id",
            },
        )["result"]

        if self.mode in ["random", "trivial", "single_item"]:
            # Check that the correct investments have been selected
            for investment in self.correct_investments:
                if investment not in [expense["number"] for expense in expenses]:
                    return (
                        0,
                        False,
                        "",
                        {"message": "Investment missing from selected list."},


validate

            skip_description = i > 0
            expense_line_sys_id = self.expense_line_sys_ids[i]
            self.tasks.append(
                DeleteExpenseLineKnapsack(
                    instance=self.instance,
                    record_number=expense_line_number,
                    record_sys_id=expense_line_sys_id,
                    fixed_config={
                        "field_name": "number",
                        "field_value": f"{expense_line_number}",
                    },
                    used_in_level_2=True,
                    is_validated=False,
                    budget=self.budget,
                    answer_format=self.answer_format,
                    level=self.level,
                    skip_description=skip_description,
                )
            )

    def validate(self, page: Page, chat_messages: List[str]) -> Tuple[float, bool, str, dict]:
        expenses = table_api_call(
            instance=self.instance,
            table="fm_expense_line",
            params={
                "sysparm_query": f"short_descriptionLIKE{self.expense_hashtag}",
                "sysparm_fields": "number,amount,sys_id",
            },
        )["result"]

        if self.mode in ["random", "trivial", "single_item"]:
            # Check that the correct investments have been selected
            for investment in self.correct_investments:
                if investment not in [expense["number"] for expense in expenses]:
                    return (
                        0,
                        False,
                        "",
                        {"message": "Investment missing from selected list."},
                    )
            # Check that the incorrect investments have not been selected
            for investment in self.incorrect_investments:
                if investment in [expense["number"] for expense in expenses]:
                    return (
                        0,
                        False,
                        "",
                        {"message": "Incorrect investment selected."},
                    )
        # In those settings, many answers are possible, it's only a matter of respecting the number of items in the solution
        elif self.mode in ["n_items", "single_item_uniform"]:
            if len(expenses) != self.num_items_uniform:
                return (
                    0,
                    False,
                    "",
                    {"message": "Incorrect number of investments selected."},
                )
        reward, done, message, info = FilterAndDoTask.validate(self, page, chat_messages)

        return reward, done, message, info

class FilterRandomExpensesAndDeleteWrongInvestmentsSmallTask(
    FilterExpenseLinesAndDeleteWrongInvestments, HumanEvalTask
):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: List[AbstractServiceNowTask] = None,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=[3, 5],
            budget=150000,
            mode="random",
            level=level,


        # In this setting there is only one valid answer
        if self.mode in ["random", "trivial", "single_item"]:
            for i, investment in enumerate(self.potential_investments):
                if i in self.selected_investment_indices:
                    self.correct_investments.append(self.expense_line_numbers[i])
                else:
                    self.incorrect_investments.append(self.expense_line_numbers[i])
        # In this setting, many answers are possible, it's only a matter of respecting the number of items in the solution
        # We store values here just so the cheat function can work uniformly
        elif self.mode in ["n_items", "single_item_uniform"]:
            for i, investment in enumerate(self.potential_investments):
                if i < self.num_items_uniform:
                    self.correct_investments.append(self.expense_line_numbers[i])
                else:
                    self.incorrect_investments.append(self.expense_line_numbers[i])

    def validate(self, page: Page, chat_messages: List[str]) -> Tuple[float, bool, str, dict]:
        super().validate(page, chat_messages)

    def check_total_return(
        self, page: Page, chat_messages: List[str]
    ) -> Tuple[float, bool, str, dict]:
        """Simple check that validates that the total return is correct."""
        if self.total_return_correct:
            return (
                1,
                True,
                "That is correct, thank you!",
                {"message": "Correct total return."},
            )

        if chat_messages and chat_messages[-1]["role"] == "assistant":
            answer = chat_messages[-1]["message"]
        else:
            return (
                0,
                False,
                "",
                {"message": "The assistant did not provide an answer."},
            )
        for format in self.alternative_max_return_formats:
            if format in answer:
                self.total_return_correct = True
                return (
                    1,
                    True,
                    "That is correct, thank you!",
                    {"message": "Correct answer."},
                )

        return (
            0,
            False,
            "",
            {"message": "Incorrect answer."},
        )

    def check_correct_investments_sent_in_chat(
        self, page: Page, chat_messages: List[str]
    ) -> Tuple[float, bool, str, dict]:
        """Check that the correct investments have been selected and their numbers have been sent in the chat"""
        if not self.investments_correctly_selected:
            if chat_messages and chat_messages[-1]["role"] == "assistant":
                answer = chat_messages[-1]["message"]
            else:
                return (
                    0,
                    False,
                    "",
                    {"message": "The assistant did not provide an answer."},
                )

            # In these settings, there is only one valid answer
            if self.mode in ["random", "trivial", "single_item"]:
                # Check that the correct investments have been selected
                for investment in self.correct_investments:


                "",
                {"message": "The assistant did not provide an answer."},
            )
        for format in self.alternative_max_return_formats:
            if format in answer:
                self.total_return_correct = True
                return (
                    1,
                    True,
                    "That is correct, thank you!",
                    {"message": "Correct answer."},
                )

        return (
            0,
            False,
            "",
            {"message": "Incorrect answer."},
        )

    def check_correct_investments_sent_in_chat(
        self, page: Page, chat_messages: List[str]
    ) -> Tuple[float, bool, str, dict]:
        """Check that the correct investments have been selected and their numbers have been sent in the chat"""
        if not self.investments_correctly_selected:
            if chat_messages and chat_messages[-1]["role"] == "assistant":
                answer = chat_messages[-1]["message"]
            else:
                return (
                    0,
                    False,
                    "",
                    {"message": "The assistant did not provide an answer."},
                )

            # In these settings, there is only one valid answer
            if self.mode in ["random", "trivial", "single_item"]:
                # Check that the correct investments have been selected
                for investment in self.correct_investments:
                    if investment not in answer:
                        return (
                            0,
                            False,
                            "",
                            {"message": "Investment missing from selected list."},
                        )
                # Check that the incorrect investments have not been selected
                for investment in self.incorrect_investments:
                    if investment in answer:
                        return (
                            0,
                            False,
                            "",
                            {"message": "Incorrect investment selected."},
                        )
            # In those settings, many answers are possible, it's only a matter of respecting the number of items in the solution
            elif self.mode in ["n_items", "single_item_uniform"]:
                # Extract the expense line numbers from the answer
                pattern = r"EXP-\w+-\w+"
                matches = re.findall(pattern, answer)
                if len(matches) != self.num_items_uniform:
                    return (
                        0,
                        False,
                        "",
                        {"message": "Incorrect number of investments selected."},
                    )
            self.correct_investments_selected = True

        return (
            1,
            True,
            "That is correct, thank you!",
            {"message": "Correct investments selected."},
        )

    def check_only_right_investment_kept(
        self, page: Page, chat_messages: List[str]
    ) -> Tuple[float, bool, str, dict]:
        """Checks that only the expected investments were kept; i.e. the others were deleted"""
        for i, investment_sys_id in enumerate(self.expense_line_sys_ids):
            record_expected = i in self.selected_investment_indices
            record_exists = table_api_call(
                instance=self.instance,
                table="fm_expense_line",
                params={"sysparm_query": f"sys_id={investment_sys_id}"},
            )["result"]
            # Missing investment that should be kept
            if record_expected and not record_exists:
                return (
                    0,
                    True,
                    "",
                    {"message": "Expected investment has been deleted."},
                )


            elif self.mode in ["n_items", "single_item_uniform"]:
                # Extract the expense line numbers from the answer
                pattern = r"EXP-\w+-\w+"
                matches = re.findall(pattern, answer)
                if len(matches) != self.num_items_uniform:
                    return (
                        0,
                        False,
                        "",
                        {"message": "Incorrect number of investments selected."},
                    )
            self.correct_investments_selected = True

        return (
            1,
            True,
            "That is correct, thank you!",
            {"message": "Correct investments selected."},
        )

    def check_only_right_investment_kept(
        self, page: Page, chat_messages: List[str]
    ) -> Tuple[float, bool, str, dict]:
        """Checks that only the expected investments were kept; i.e. the others were deleted"""
        for i, investment_sys_id in enumerate(self.expense_line_sys_ids):
            record_expected = i in self.selected_investment_indices
            record_exists = table_api_call(
                instance=self.instance,
                table="fm_expense_line",
                params={"sysparm_query": f"sys_id={investment_sys_id}"},
            )["result"]
            # Missing investment that should be kept
            if record_expected and not record_exists:
                return (
                    0,
                    True,
                    "",
                    {"message": "Expected investment has been deleted."},
                )
            # Unexpected investment that should be deleted
            if not record_expected and record_exists:
                return (
                    0,
                    False,
                    "",
                    {"message": "Unexpected investment is present."},
                )

        return (
            1,
            True,
            "That is correct, thank you!",
            {"message": "Correct investments kept."},
        )

    def teardown(self) -> None:
        for expense_sys_id in self.expense_line_sys_ids:
            record_exists = table_api_call(
                instance=self.instance,
                table="fm_expense_line",
                params={"sysparm_query": f"sys_id={expense_sys_id}"},
            )["result"]
            if record_exists:
                db_delete_from_table(
                    instance=self.instance,
                    table="fm_expense_line",
                    sys_id=expense_sys_id,
                )
        super().teardown()

class FilterExpensesAndFindTotalReturnTask(FilterExpensesAndAllocateInvestmentsTask):
    def __init__(
        self,


teardown

                    True,
                    "",
                    {"message": "Expected investment has been deleted."},
                )
            # Unexpected investment that should be deleted
            if not record_expected and record_exists:
                return (
                    0,
                    False,
                    "",
                    {"message": "Unexpected investment is present."},
                )

        return (
            1,
            True,
            "That is correct, thank you!",
            {"message": "Correct investments kept."},
        )

    def teardown(self) -> None:
        for expense_sys_id in self.expense_line_sys_ids:
            record_exists = table_api_call(
                instance=self.instance,
                table="fm_expense_line",
                params={"sysparm_query": f"sys_id={expense_sys_id}"},
            )["result"]
            if record_exists:
                db_delete_from_table(
                    instance=self.instance,
                    table="fm_expense_line",
                    sys_id=expense_sys_id,
                )
        super().teardown()

class FilterExpensesAndFindTotalReturnTask(FilterExpensesAndAllocateInvestmentsTask):
    def __init__(
        self,
        seed: int,
        instance: SNowInstance = None,
        fixed_config: list[AbstractServiceNowTask] = None,
        num_expenses: list[int] = [3, 4],
        budget: int = 150000,
        mode: str = "random",
        answer_format: str = "total_return_only",
        num_items_uniform: int = 1,
        level: int = 2,
    ):
        super().__init__(
            seed,
            instance,
            fixed_config,
            num_expenses=num_expenses,


from playwright.sync_api._generated import Page
from typing import Tuple

from .dash_do_base import DashboardRetrieveIncidentAndDoInfeasibleTask, DashDoFinalTask
from .utils.infeasible_configs import get_infeasible_form_config

from ..base import AbstractServiceNowTask
from ..dashboard import SingleChartMinMaxRetrievalTask

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.form import CreateProblemTask

class DashboardRetrieveIncidentAndCreateProblemInfeasibleTask(
    DashboardRetrieveIncidentAndDoInfeasibleTask
):
    def __init__(
        self,
        instance: SNowInstance = None,
        seed: int = None,
        fixed_config: list[AbstractServiceNowTask] = None,
        level: int = 2,
        question: str = None,
        dashboard_class: AbstractServiceNowTask = None,
        provide_reason: bool = None,
    ) -> None:
        """
        Retrieve the worst performing employee and create a problem to assign them a probation course.
        Attributes:
        -----------
        task_description: str
            The start of the task description to be completed.
        short_description: str
            A short description of the task to be completed. "Create a problem for the worst performing employee from the list"
        """
        super().__init__(
            instance=instance,
            seed=seed,
            fixed_config=fixed_config,
            level=level,
            question=question,
            dashboard_class=dashboard_class,
            function=get_infeasible_form_config,
            provide_reason=provide_reason,
        )
        self.task_description = ""
        self.short_description = "Create a problem for the worst performing employee from the list."
        self.attribute_name = "assigned_to"
        self.filter_than = "lesser"
        self.prefix = "ICP"

    def set_compositional_task(self) -> None:
        agent_full_names, agent_value_sysids = self.get_agent_values(
            self.attribute_name, self.filter_than
        )
        self.agent_value_sysids = agent_value_sysids
        create_problem_subtasks = []
        for agent_full_name in agent_full_names:
            problem_config = {
                "fields": {
                    "short_description": "Problem statement",
                    "description": "Description",
                    "urgency": "Urgency",
                    "assigned_to": "Assigned to",
                    "impact": "Impact",
                },
                "task_fields": [
                    "short_description",
                    "description",
                    "urgency",
                    "assigned_to",
                    "impact",
                ],
                "template_record": {
                    "description": "Compulsory training for employee in probation",
                    "short_description": "Compulsory training for employee in probation",
                    "impact": "1 - High",
                    "urgency": "1 - High",
                    "assigned_to": agent_full_name,
                },
                "infeasible_task_fields": ["short_description", "description", "urgency", "impact"],
            }
            problem_config, self.infeasible_reasons = self.function(
                config=problem_config, random=self.random
            )

            create_problem_subtask = [
                # Navigate to the incident 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,
                ),
                # Create a problem
                CreateProblemTask(
                    instance=self.instance,
                    fixed_config=problem_config,
                    is_validated=False,
                    used_in_level_2=True,
                    check_record_created=False,
                ),
            ]
            create_problem_subtasks += create_problem_subtask

        self.compositional_task = create_problem_subtasks

    def setup_goal(self, page: Page) -> tuple[str, dict]:
        self.create_report(
            user_roles=[
                "itil",
                "problem_admin",
                "problem_manager",
                "problem_coordinator",
                "problem_task_analyst",
            ]
        )
        self.set_compositional_task()
        config = self.fixed_config if self.fixed_config else self._get_config()
        if self.level == 3:
            self.task_description = (
                self.task_description
                + f"Value to retrieve: {self.description_mapping[self.question]} of all the incidents. Comparator: Less than or equal to the value.\n"
                + f"Task: Create problems with the following information: \n"
                + f"Category: 'Inquiry / Help', Impact: '1 - High', Urgency: '1 - High', Short description: 'Compulsory training for employee in probation'.\n"
                + f"Assign the problems you create to the agents mentioned above."
            )

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

        if self.level == 2:
            goal = (
                self.task_description
                + f"\n1. Navigate to the CMDB reports and look for the report with the mentioned hashtag. \n"
                + f"\n2. Find the agents with number of incidents less than or equal to the  {self.description_mapping[self.question]} of the incidents assigned to every one. \n"
                + f"\n3. Navigate to All > Problems. \n"
                + f"\n4. Create new problems with the following field values:- Category: 'Inquiry / Help', Impact: '1 - High', Urgency: '1 - High', Short description: 'Compulsory training for employee in probation' and assign them to each of the agents."
                + "\nYou will create as many problems as there are agents.\n"
            )

        return goal, info

    def teardown(self) -> None:
        for agent_sysid in self.agent_sysids:
            created_problem_response = table_api_call(
                instance=self.instance,
                table="problem",
                params={
                    "sysparm_query": f"assigned_to={agent_sysid}",
                },
                method="GET",
            )["result"]
            for problem in created_problem_response:
                db_delete_from_table(
                    instance=self.instance,
                    table="problem",
                    sys_id=problem["sys_id"],
                )
        return super().teardown()

class DashboardRetrieveIncidentAndMinCreateProblemInfeasibleWithReasonTask(
    DashboardRetrieveIncidentAndCreateProblemInfeasibleTask, DashDoFinalTask
):
    def __init__(
        self,
        instance: SNowInstance = None,
        seed: int = None,
        fixed_config: list[AbstractServiceNowTask] = None,
        level: int = 2,
    ) -> None:
        """
        Retrieve the worst performing employee and create a problem to assign them a probation course.
        Attributes:
        -----------
        task_description: str
            The start of the task description to be completed.
        short_description: str
            A short description of the task to be completed. "Create a problem for the worst performing employee from the list"
        """
        super().__init__(
            instance=instance,
            seed=seed,
            fixed_config=fixed_config,
            level=level,
            question="min",
            dashboard_class=SingleChartMinMaxRetrievalTask,
            provide_reason=True,
        )

class DashboardRetrieveIncidentAndMinCreateProblemInfeasibleTask(
    DashboardRetrieveIncidentAndCreateProblemInfeasibleTask, DashDoFinalTask
):
    def __init__(
        self,
        instance: SNowInstance = None,
        seed: int = None,
        fixed_config: list[AbstractServiceNowTask] = None,
        level: int = 2,
    ) -> None:
        """
        Retrieve the worst performing employee and create a problem to assign them a probation course.
        Attributes:
        -----------
        task_description: str
            The start of the task description to be completed.
        short_description: str
            A short description of the task to be completed. "Create a problem for the worst performing employee from the list"
        """
        super().__init__(
            instance=instance,
            seed=seed,
            fixed_config=fixed_config,
            level=level,
            question="min",
            dashboard_class=SingleChartMinMaxRetrievalTask,
            provide_reason=False,
        )

local_vars = locals().copy()

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


from playwright.sync_api._generated import Page
from typing import Tuple

from .dash_do_base import DashboardRetrieveIncidentAndDoInfeasibleTask, DashDoFinalTask
from .utils.infeasible_configs import get_infeasible_form_config

from ..base import AbstractServiceNowTask
from ..dashboard import SingleChartMinMaxRetrievalTask

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.form import CreateProblemTask

class DashboardRetrieveIncidentAndCreateProblemInfeasibleTask(
    DashboardRetrieveIncidentAndDoInfeasibleTask
):
    def __init__(
        self,
        instance: SNowInstance = None,
        seed: int = None,
        fixed_config: list[AbstractServiceNowTask] = None,
        level: int = 2,
        question: str = None,
        dashboard_class: AbstractServiceNowTask = None,
        provide_reason: bool = None,
    ) -> None:
        """
        Retrieve the worst performing employee and create a problem to assign them a probation course.
        Attributes:
        -----------
        task_description: str
            The start of the task description to be completed.
        short_description: str
            A short description of the task to be completed. "Create a problem for the worst performing employee from the list"
        """
        super().__init__(
            instance=instance,
            seed=seed,
            fixed_config=fixed_config,
            level=level,
            question=question,
            dashboard_class=dashboard_class,
            function=get_infeasible_form_config,
            provide_reason=provide_reason,
        )
        self.task_description = ""
        self.short_description = "Create a problem for the worst performing employee from the list."
        self.attribute_name = "assigned_to"
        self.filter_than = "lesser"
        self.prefix = "ICP"

    def set_compositional_task(self) -> None:
        agent_full_names, agent_value_sysids = self.get_agent_values(
            self.attribute_name, self.filter_than
        )
        self.agent_value_sysids = agent_value_sysids
        create_problem_subtasks = []
        for agent_full_name in agent_full_names:
            problem_config = {
                "fields": {
                    "short_description": "Problem statement",
                    "description": "Description",
                    "urgency": "Urgency",
                    "assigned_to": "Assigned to",
                    "impact": "Impact",
                },
                "task_fields": [
                    "short_description",
                    "description",
                    "urgency",
                    "assigned_to",
                    "impact",
                ],
                "template_record": {
                    "description": "Compulsory training for employee in probation",
                    "short_description": "Compulsory training for employee in probation",
                    "impact": "1 - High",
                    "urgency": "1 - High",
                    "assigned_to": agent_full_name,
                },
                "infeasible_task_fields": ["short_description", "description", "urgency", "impact"],
            }
            problem_config, self.infeasible_reasons = self.function(
                config=problem_config, random=self.random
            )

            create_problem_subtask = [
                # Navigate to the incident 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,
                ),
                # Create a problem
                CreateProblemTask(
                    instance=self.instance,
                    fixed_config=problem_config,
                    is_validated=False,
                    used_in_level_2=True,
                    check_record_created=False,
                ),
            ]
            create_problem_subtasks += create_problem_subtask

        self.compositional_task = create_problem_subtasks

    def setup_goal(self, page: Page) -> tuple[str, dict]:
        self.create_report(
            user_roles=[
                "itil",
                "problem_admin",
                "problem_manager",
                "problem_coordinator",
                "problem_task_analyst",
            ]
        )
        self.set_compositional_task()
        config = self.fixed_config if self.fixed_config else self._get_config()
        if self.level == 3:
            self.task_description = (
                self.task_description
                + f"Value to retrieve: {self.description_mapping[self.question]} of all the incidents. Comparator: Less than or equal to the value.\n"
                + f"Task: Create problems with the following information: \n"
                + f"Category: 'Inquiry / Help', Impact: '1 - High', Urgency: '1 - High', Short description: 'Compulsory training for employee in probation'.\n"
                + f"Assign the problems you create to the agents mentioned above."
            )

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

        if self.level == 2:
            goal = (
                self.task_description
                + f"\n1. Navigate to the CMDB reports and look for the report with the mentioned hashtag. \n"
                + f"\n2. Find the agents with number of incidents less than or equal to the  {self.description_mapping[self.question]} of the incidents assigned to every one. \n"
                + f"\n3. Navigate to All > Problems. \n"
                + f"\n4. Create new problems with the following field values:- Category: 'Inquiry / Help', Impact: '1 - High', Urgency: '1 - High', Short description: 'Compulsory training for employee in probation' and assign them to each of the agents."
                + "\nYou will create as many problems as there are agents.\n"
            )

        return goal, info

    def teardown(self) -> None:
        for agent_sysid in self.agent_sysids:
            created_problem_response = table_api_call(
                instance=self.instance,
                table="problem",
                params={
                    "sysparm_query": f"assigned_to={agent_sysid}",
                },
                method="GET",
            )["result"]
            for problem in created_problem_response:
                db_delete_from_table(
                    instance=self.instance,
                    table="problem",
                    sys_id=problem["sys_id"],
                )
        return super().teardown()

class DashboardRetrieveIncidentAndMinCreateProblemInfeasibleWithReasonTask(
    DashboardRetrieveIncidentAndCreateProblemInfeasibleTask, DashDoFinalTask
):
    def __init__(
        self,
        instance: SNowInstance = None,
        seed: int = None,
        fixed_config: list[AbstractServiceNowTask] = None,
        level: int = 2,
    ) -> None:
        """
        Retrieve the worst performing employee and create a problem to assign them a probation course.
        Attributes:
        -----------
        task_description: str
            The start of the task description to be completed.
        short_description: str
            A short description of the task to be completed. "Create a problem for the worst performing employee from the list"


    def teardown(self) -> None:
        for agent_sysid in self.agent_sysids:
            created_problem_response = table_api_call(
                instance=self.instance,
                table="problem",
                params={
                    "sysparm_query": f"assigned_to={agent_sysid}",
                },
                method="GET",
            )["result"]
            for problem in created_problem_response:
                db_delete_from_table(
                    instance=self.instance,
                    table="problem",
                    sys_id=problem["sys_id"],
                )
        return super().teardown()

class DashboardRetrieveIncidentAndMinCreateProblemInfeasibleWithReasonTask(
    DashboardRetrieveIncidentAndCreateProblemInfeasibleTask, DashDoFinalTask
):
    def __init__(
        self,
        instance: SNowInstance = None,
        seed: int = None,
        fixed_config: list[AbstractServiceNowTask] = None,
        level: int = 2,
    ) -> None:
        """
        Retrieve the worst performing employee and create a problem to assign them a probation course.
        Attributes:
        -----------
        task_description: str
            The start of the task description to be completed.
        short_description: str
            A short description of the task to be completed. "Create a problem for the worst performing employee from the list"
        """
        super().__init__(
            instance=instance,
            seed=seed,
            fixed_config=fixed_config,
            level=level,
            question="min",
            dashboard_class=SingleChartMinMaxRetrievalTask,
            provide_reason=True,
        )

class DashboardRetrieveIncidentAndMinCreateProblemInfeasibleTask(
    DashboardRetrieveIncidentAndCreateProblemInfeasibleTask, DashDoFinalTask
):
    def __init__(
        self,
        instance: SNowInstance = None,
        seed: int = None,
        fixed_config: list[AbstractServiceNowTask] = None,
        level: int = 2,
    ) -> None:
        """
        Retrieve the worst performing employee and create a problem to assign them a probation course.
        Attributes:
        -----------
        task_description: str
            The start of the task description to be completed.
        short_description: str
            A short description of the task to be completed. "Create a problem for the worst performing employee from the list"


        """
        Retrieve the worst performing employee and create a problem to assign them a probation course.
        Attributes:
        -----------
        task_description: str
            The start of the task description to be completed.
        short_description: str
            A short description of the task to be completed. "Create a problem for the worst performing employee from the list"
        """
        super().__init__(
            instance=instance,
            seed=seed,
            fixed_config=fixed_config,
            level=level,
            question="min",
            dashboard_class=SingleChartMinMaxRetrievalTask,
            provide_reason=True,
        )

class DashboardRetrieveIncidentAndMinCreateProblemInfeasibleTask(
    DashboardRetrieveIncidentAndCreateProblemInfeasibleTask, DashDoFinalTask
):
    def __init__(
        self,
        instance: SNowInstance = None,
        seed: int = None,
        fixed_config: list[AbstractServiceNowTask] = None,
        level: int = 2,
    ) -> None:
        """
        Retrieve the worst performing employee and create a problem to assign them a probation course.
        Attributes:
        -----------
        task_description: str
            The start of the task description to be completed.
        short_description: str
            A short description of the task to be completed. "Create a problem for the worst performing employee from the list"
        """
        super().__init__(
            instance=instance,
            seed=seed,
            fixed_config=fixed_config,
            level=level,
            question="min",
            dashboard_class=SingleChartMinMaxRetrievalTask,
            provide_reason=False,
        )

local_vars = locals().copy()

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


        -----------
        task_description: str
            The start of the task description to be completed.
        short_description: str
            A short description of the task to be completed. "Create a problem for the worst performing employee from the list"
        """
        super().__init__(
            instance=instance,
            seed=seed,
            fixed_config=fixed_config,
            level=level,
            question="min",
            dashboard_class=SingleChartMinMaxRetrievalTask,
            provide_reason=True,
        )

class DashboardRetrieveIncidentAndMinCreateProblemInfeasibleTask(
    DashboardRetrieveIncidentAndCreateProblemInfeasibleTask, DashDoFinalTask
):
    def __init__(
        self,
        instance: SNowInstance = None,
        seed: int = None,
        fixed_config: list[AbstractServiceNowTask] = None,
        level: int = 2,
    ) -> None:
        """
        Retrieve the worst performing employee and create a problem to assign them a probation course.
        Attributes:
        -----------
        task_description: str
            The start of the task description to be completed.
        short_description: str
            A short description of the task to be completed. "Create a problem for the worst performing employee from the list"
        """
        super().__init__(
            instance=instance,
            seed=seed,
            fixed_config=fixed_config,
            level=level,
            question="min",
            dashboard_class=SingleChartMinMaxRetrievalTask,
            provide_reason=False,
        )

local_vars = locals().copy()

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


            The start of the task description to be completed.
        short_description: str
            A short description of the task to be completed. "Create a problem for the worst performing employee from the list"
        """
        super().__init__(
            instance=instance,
            seed=seed,
            fixed_config=fixed_config,
            level=level,
            question=question,
            dashboard_class=dashboard_class,
            function=get_infeasible_form_config,
            provide_reason=provide_reason,
        )
        self.task_description = ""
        self.short_description = "Create a problem for the worst performing employee from the list."
        self.attribute_name = "assigned_to"
        self.filter_than = "lesser"
        self.prefix = "ICP"

    def set_compositional_task(self) -> None:
        agent_full_names, agent_value_sysids = self.get_agent_values(
            self.attribute_name, self.filter_than
        )
        self.agent_value_sysids = agent_value_sysids
        create_problem_subtasks = []
        for agent_full_name in agent_full_names:
            problem_config = {
                "fields": {
                    "short_description": "Problem statement",
                    "description": "Description",
                    "urgency": "Urgency",
                    "assigned_to": "Assigned to",
                    "impact": "Impact",
                },
                "task_fields": [
                    "short_description",
                    "description",
                    "urgency",
                    "assigned_to",
                    "impact",
                ],
                "template_record": {
                    "description": "Compulsory training for employee in probation",
                    "short_description": "Compulsory training for employee in probation",
                    "impact": "1 - High",
                    "urgency": "1 - High",
                    "assigned_to": agent_full_name,
                },
                "infeasible_task_fields": ["short_description", "description", "urgency", "impact"],
            }
            problem_config, self.infeasible_reasons = self.function(
                config=problem_config, random=self.random
            )

            create_problem_subtask = [
                # Navigate to the incident 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,
                ),
                # Create a problem
                CreateProblemTask(
                    instance=self.instance,
                    fixed_config=problem_config,
                    is_validated=False,
                    used_in_level_2=True,
                    check_record_created=False,
                ),
            ]
            create_problem_subtasks += create_problem_subtask

        self.compositional_task = create_problem_subtasks

    def setup_goal(self, page: Page) -> tuple[str, dict]:
        self.create_report(
            user_roles=[
                "itil",
                "problem_admin",
                "problem_manager",
                "problem_coordinator",
                "problem_task_analyst",
            ]
        )
        self.set_compositional_task()
        config = self.fixed_config if self.fixed_config else self._get_config()
        if self.level == 3:
            self.task_description = (
                self.task_description
                + f"Value to retrieve: {self.description_mapping[self.question]} of all the incidents. Comparator: Less than or equal to the value.\n"
                + f"Task: Create problems with the following information: \n"
                + f"Category: 'Inquiry / Help', Impact: '1 - High', Urgency: '1 - High', Short description: 'Compulsory training for employee in probation'.\n"
                + f"Assign the problems you create to the agents mentioned above."


                        "application": "Problem",
                        "module": "All",
                        "url": "/now/nav/ui/classic/params/target/problem_list.do",
                    },
                    is_validated=False,
                    used_in_level_2=True,
                ),
                # Create a problem
                CreateProblemTask(
                    instance=self.instance,
                    fixed_config=problem_config,
                    is_validated=False,
                    used_in_level_2=True,
                    check_record_created=False,
                ),
            ]
            create_problem_subtasks += create_problem_subtask

        self.compositional_task = create_problem_subtasks

    def setup_goal(self, page: Page) -> tuple[str, dict]:
        self.create_report(
            user_roles=[
                "itil",
                "problem_admin",
                "problem_manager",
                "problem_coordinator",
                "problem_task_analyst",
            ]
        )
        self.set_compositional_task()
        config = self.fixed_config if self.fixed_config else self._get_config()
        if self.level == 3:
            self.task_description = (
                self.task_description
                + f"Value to retrieve: {self.description_mapping[self.question]} of all the incidents. Comparator: Less than or equal to the value.\n"
                + f"Task: Create problems with the following information: \n"
                + f"Category: 'Inquiry / Help', Impact: '1 - High', Urgency: '1 - High', Short description: 'Compulsory training for employee in probation'.\n"
                + f"Assign the problems you create to the agents mentioned above."
            )

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

        if self.level == 2:
            goal = (
                self.task_description
                + f"\n1. Navigate to the CMDB reports and look for the report with the mentioned hashtag. \n"
                + f"\n2. Find the agents with number of incidents less than or equal to the  {self.description_mapping[self.question]} of the incidents assigned to every one. \n"
                + f"\n3. Navigate to All > Problems. \n"
                + f"\n4. Create new problems with the following field values:- Category: 'Inquiry / Help', Impact: '1 - High', Urgency: '1 - High', Short description: 'Compulsory training for employee in probation' and assign them to each of the agents."
                + "\nYou will create as many problems as there are agents.\n"
            )

        return goal, info

    def teardown(self) -> None:
        for agent_sysid in self.agent_sysids:
            created_problem_response = table_api_call(
                instance=self.instance,
                table="problem",
                params={
                    "sysparm_query": f"assigned_to={agent_sysid}",
                },
                method="GET",
            )["result"]
            for problem in created_problem_response:
                db_delete_from_table(
                    instance=self.instance,
                    table="problem",
                    sys_id=problem["sys_id"],
                )
        return super().teardown()


                + f"Value to retrieve: {self.description_mapping[self.question]} of all the incidents. Comparator: Less than or equal to the value.\n"
                + f"Task: Create problems with the following information: \n"
                + f"Category: 'Inquiry / Help', Impact: '1 - High', Urgency: '1 - High', Short description: 'Compulsory training for employee in probation'.\n"
                + f"Assign the problems you create to the agents mentioned above."
            )

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

        if self.level == 2:
            goal = (
                self.task_description
                + f"\n1. Navigate to the CMDB reports and look for the report with the mentioned hashtag. \n"
                + f"\n2. Find the agents with number of incidents less than or equal to the  {self.description_mapping[self.question]} of the incidents assigned to every one. \n"
                + f"\n3. Navigate to All > Problems. \n"
                + f"\n4. Create new problems with the following field values:- Category: 'Inquiry / Help', Impact: '1 - High', Urgency: '1 - High', Short description: 'Compulsory training for employee in probation' and assign them to each of the agents."
                + "\nYou will create as many problems as there are agents.\n"
            )

        return goal, info

    def teardown(self) -> None:
        for agent_sysid in self.agent_sysids:
            created_problem_response = table_api_call(
                instance=self.instance,
                table="problem",
                params={
                    "sysparm_query": f"assigned_to={agent_sysid}",
                },
                method="GET",
            )["result"]
            for problem in created_problem_response:
                db_delete_from_table(
                    instance=self.instance,
                    table="problem",
                    sys_id=problem["sys_id"],
                )
        return super().teardown()

class DashboardRetrieveIncidentAndMinCreateProblemInfeasibleWithReasonTask(
    DashboardRetrieveIncidentAndCreateProblemInfeasibleTask, DashDoFinalTask
):
    def __init__(
        self,
        instance: SNowInstance = None,
        seed: int = None,
        fixed_config: list[AbstractServiceNowTask] = None,
        level: int = 2,
    ) -> None:
        """
        Retrieve the worst performing employee and create a problem to assign them a probation course.
        Attributes:
        -----------
        task_description: str
            The start of the task description to be completed.
        short_description: str
            A short description of the task to be completed. "Create a problem for the worst performing employee from the list"


import faker

fake = faker.Faker()

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,
            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
]