browsergym-workarena 0.2.0__py3-none-any.whl → 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- browsergym/workarena/__init__.py +13 -1
- browsergym/workarena/api/category.py +74 -0
- browsergym/workarena/api/change_request.py +87 -0
- browsergym/workarena/api/computer_asset.py +90 -0
- browsergym/workarena/api/cost_center.py +19 -0
- browsergym/workarena/api/expense_line.py +89 -0
- browsergym/workarena/api/incident.py +45 -0
- browsergym/workarena/api/knowledge.py +29 -0
- browsergym/workarena/api/problem.py +90 -0
- browsergym/workarena/api/report.py +183 -0
- browsergym/workarena/api/requested_items.py +63 -0
- browsergym/workarena/api/user.py +11 -8
- browsergym/workarena/api/utils.py +47 -3
- browsergym/workarena/config.py +21 -1
- browsergym/workarena/data_files/setup_files/forms/expected_incident_form_fields.json +1 -1
- browsergym/workarena/data_files/setup_files/forms/expected_request_item_form_fields.json +1 -0
- browsergym/workarena/data_files/setup_files/knowledge/protocols.json +46 -0
- browsergym/workarena/data_files/setup_files/knowledge/test.html +1 -0
- browsergym/workarena/data_files/setup_files/lists/expected_asset_list_columns.json +2 -24
- browsergym/workarena/data_files/setup_files/lists/expected_change_request_list_columns.json +4 -40
- browsergym/workarena/data_files/setup_files/lists/expected_expense_line_list_columns.json +12 -0
- browsergym/workarena/data_files/setup_files/lists/expected_hardware_list_columns.json +1 -42
- browsergym/workarena/data_files/setup_files/lists/expected_incident_list_columns.json +2 -18
- browsergym/workarena/data_files/setup_files/lists/expected_problem_list_columns.json +12 -0
- browsergym/workarena/data_files/setup_files/lists/expected_requested_items_list_columns.json +12 -0
- browsergym/workarena/data_files/setup_files/lists/expected_service_catalog_list_columns.json +2 -19
- browsergym/workarena/data_files/setup_files/lists/expected_user_list_columns.json +3 -50
- browsergym/workarena/data_files/task_configs/all_menu.json +95 -95
- browsergym/workarena/data_files/task_configs/dashboard_retrieval_minmax_task.json +1 -1
- browsergym/workarena/data_files/task_configs/dashboard_retrieval_value_task.json +1 -1
- browsergym/workarena/data_files/task_configs/filter_service_catalog_item_list_task.json +7986 -7982
- browsergym/workarena/data_files/task_configs/impersonation_users.json +3 -3
- browsergym/workarena/data_files/task_configs/report_retrieval_minmax_task.json +1 -1
- browsergym/workarena/data_files/task_configs/report_retrieval_value_task.json +1 -1
- browsergym/workarena/human_eval/console.js +176 -0
- browsergym/workarena/human_eval/tool.py +366 -0
- browsergym/workarena/install.py +81 -20
- browsergym/workarena/tasks/base.py +55 -20
- browsergym/workarena/tasks/comp_building_block.py +4 -0
- browsergym/workarena/tasks/compositional/__init__.py +76 -0
- browsergym/workarena/tasks/compositional/base.py +364 -0
- browsergym/workarena/tasks/compositional/dash_do_base.py +1366 -0
- browsergym/workarena/tasks/compositional/dash_do_catalog.py +1127 -0
- browsergym/workarena/tasks/compositional/dash_do_catalog_infeasible.py +2047 -0
- browsergym/workarena/tasks/compositional/dash_do_create_incident.py +403 -0
- browsergym/workarena/tasks/compositional/dash_do_create_incident_infeasible.py +278 -0
- browsergym/workarena/tasks/compositional/dash_do_create_problem.py +336 -0
- browsergym/workarena/tasks/compositional/dash_do_create_problem_infeasible.py +235 -0
- browsergym/workarena/tasks/compositional/dash_do_filter.py +1600 -0
- browsergym/workarena/tasks/compositional/dash_do_request_item.py +1315 -0
- browsergym/workarena/tasks/compositional/dash_do_request_item_infeasible.py +693 -0
- browsergym/workarena/tasks/compositional/delete_record.py +341 -0
- browsergym/workarena/tasks/compositional/edit_knowledge_base.py +457 -0
- browsergym/workarena/tasks/compositional/expense_management.py +598 -0
- browsergym/workarena/tasks/compositional/filter_and_do.py +139 -0
- browsergym/workarena/tasks/compositional/find_and_order_item.py +345 -0
- browsergym/workarena/tasks/compositional/manage_change_request_schedule.py +1417 -0
- browsergym/workarena/tasks/compositional/mark_duplicate_problems.py +499 -0
- browsergym/workarena/tasks/compositional/maximize_investment_return.py +1763 -0
- browsergym/workarena/tasks/compositional/navigate_and_do.py +1151 -0
- browsergym/workarena/tasks/compositional/navigate_and_do_infeasible.py +2100 -0
- browsergym/workarena/tasks/compositional/offboard_user.py +207 -0
- browsergym/workarena/tasks/compositional/onboard_user.py +226 -0
- browsergym/workarena/tasks/compositional/update_task.py +145 -0
- browsergym/workarena/tasks/compositional/utils/curriculum.py +215 -0
- browsergym/workarena/tasks/compositional/utils/infeasible_configs.py +151 -0
- browsergym/workarena/tasks/compositional/utils/knapsack.py +192 -0
- browsergym/workarena/tasks/compositional/warranty_check.py +227 -0
- browsergym/workarena/tasks/compositional/work_assignment.py +804 -0
- browsergym/workarena/tasks/compositional/workload_balancing.py +396 -0
- browsergym/workarena/tasks/dashboard.py +188 -8
- browsergym/workarena/tasks/form.py +1024 -232
- browsergym/workarena/tasks/knowledge.py +216 -25
- browsergym/workarena/tasks/list.py +519 -102
- browsergym/workarena/tasks/mark_duplicate_problem.py +171 -0
- browsergym/workarena/tasks/navigation.py +55 -13
- browsergym/workarena/tasks/scripts/extract_all_menu_items.py +9 -2
- browsergym/workarena/tasks/scripts/generate_dashboard_configs.py +6 -5
- browsergym/workarena/tasks/scripts/service_catalog.py +2 -1
- browsergym/workarena/tasks/scripts/validate.py +8 -2
- browsergym/workarena/tasks/send_chat_message.py +90 -0
- browsergym/workarena/tasks/service_catalog.py +94 -26
- browsergym/workarena/tasks/utils/form.py +1 -4
- browsergym/workarena/tasks/utils/private_tasks.py +63 -0
- browsergym/workarena/tasks/utils/utils.py +13 -0
- {browsergym_workarena-0.2.0.dist-info → browsergym_workarena-0.3.0.dist-info}/METADATA +27 -20
- browsergym_workarena-0.3.0.dist-info/RECORD +138 -0
- {browsergym_workarena-0.2.0.dist-info → browsergym_workarena-0.3.0.dist-info}/entry_points.txt +1 -0
- browsergym_workarena-0.2.0.dist-info/RECORD +0 -85
- {browsergym_workarena-0.2.0.dist-info → browsergym_workarena-0.3.0.dist-info}/WHEEL +0 -0
- {browsergym_workarena-0.2.0.dist-info → browsergym_workarena-0.3.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from datetime import timedelta
|
|
4
|
+
from faker import Faker
|
|
5
|
+
from typing import List, Tuple
|
|
6
|
+
|
|
7
|
+
fake = Faker()
|
|
8
|
+
|
|
9
|
+
from playwright.sync_api._generated import Page
|
|
10
|
+
|
|
11
|
+
from .base import HumanEvalTask
|
|
12
|
+
from .delete_record import DeleteExpenseLineExpenseManagementTask
|
|
13
|
+
from .filter_and_do import FilterAndDoTask
|
|
14
|
+
|
|
15
|
+
from ..base import AbstractServiceNowTask
|
|
16
|
+
|
|
17
|
+
from ...api.change_request import create_change_request
|
|
18
|
+
from ...api.expense_line import create_expense_line
|
|
19
|
+
from ...api.utils import table_api_call, db_delete_from_table
|
|
20
|
+
from ...config import (
|
|
21
|
+
# Expected columns for the different lists
|
|
22
|
+
EXPECTED_EXPENSE_LINE_COLUMNS_PATH,
|
|
23
|
+
)
|
|
24
|
+
from ...instance import SNowInstance
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ExpenseManagementTask(FilterAndDoTask):
|
|
28
|
+
"""Task to manage expenses.
|
|
29
|
+
Args:
|
|
30
|
+
|
|
31
|
+
num_duplicates: int
|
|
32
|
+
The number of duplicate expenses to create
|
|
33
|
+
extra_expenses: int
|
|
34
|
+
The number of extra expenses to create (total expenses will be num_duplicates + extra_expenses)
|
|
35
|
+
goal_type: str
|
|
36
|
+
The type of goal to generate. Choice of "base", "date", "amount", "any".
|
|
37
|
+
- "base": one expense is linked to a change request, others are not and are expected to be deleted
|
|
38
|
+
- "date": none of the expenses are linked to change requests; the oldest one is expeted to be deleted
|
|
39
|
+
- "amount": none of the expenses are linked to change requests and they are all created on the same date;
|
|
40
|
+
the most expensive one is expeted to be deleted
|
|
41
|
+
- "any": any of the expenses can be deleted
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
min_allowed_amount = 100
|
|
45
|
+
max_allowed_amount = 10000
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
seed: int,
|
|
50
|
+
instance: SNowInstance = None,
|
|
51
|
+
fixed_config: list[AbstractServiceNowTask] = None,
|
|
52
|
+
num_duplicates: int = 2,
|
|
53
|
+
extra_expenses: int = 2,
|
|
54
|
+
goal_type: str = "base",
|
|
55
|
+
level: int = 2,
|
|
56
|
+
) -> None:
|
|
57
|
+
super().__init__(
|
|
58
|
+
seed=seed,
|
|
59
|
+
instance=instance,
|
|
60
|
+
fixed_config=fixed_config,
|
|
61
|
+
navigation_config={
|
|
62
|
+
"module": "Expense Lines",
|
|
63
|
+
"application": "Cost",
|
|
64
|
+
},
|
|
65
|
+
level=level,
|
|
66
|
+
protocol_name="Managing Your Existing Expenses",
|
|
67
|
+
)
|
|
68
|
+
self.num_duplicates = num_duplicates
|
|
69
|
+
self.extra_expenses = extra_expenses
|
|
70
|
+
self.total_expenses = num_duplicates + extra_expenses
|
|
71
|
+
self.goal_type = goal_type
|
|
72
|
+
self.change_request_sysids = []
|
|
73
|
+
|
|
74
|
+
# mappings between number -> (is_duplicate, sys_id)
|
|
75
|
+
self.expense_lines = {}
|
|
76
|
+
self.expense_to_keep_number = None # The number of the expense that will be kept; i.e. not deleted by the cheat/agent if successful
|
|
77
|
+
|
|
78
|
+
self.expense_hashtag = "#SERIES-" + self.unique_id[:10]
|
|
79
|
+
self.short_description = f"Managing Your Existing Expenses"
|
|
80
|
+
self.task_description = f'Referring to company protocol "{self.protocol_name}" (located in the "Company Protocols" knowledge base) manage your expenses with short description containing hashtag {self.expense_hashtag}. '
|
|
81
|
+
self.tasks = []
|
|
82
|
+
|
|
83
|
+
def _setup_list(self) -> None:
|
|
84
|
+
"""The setup might be a bit complex on a first read. To understand it better, refer to the protocol for this task."""
|
|
85
|
+
self.filter_config = {
|
|
86
|
+
"list_url": "/now/nav/ui/classic/params/target/fm_expense_line_list.do",
|
|
87
|
+
"expected_fields_path": EXPECTED_EXPENSE_LINE_COLUMNS_PATH,
|
|
88
|
+
"filter_columns": [
|
|
89
|
+
"short_description",
|
|
90
|
+
],
|
|
91
|
+
"filter_kind": "AND",
|
|
92
|
+
"filter_operators": ["contains"],
|
|
93
|
+
"filter_values": [
|
|
94
|
+
f"{self.expense_hashtag}",
|
|
95
|
+
],
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# Short description to use for duplicate expenses
|
|
99
|
+
duplicate_short_description = f"{fake.sentence(4)}"
|
|
100
|
+
|
|
101
|
+
# Set the default amount and date in case uniform_amount and uniform_date are True
|
|
102
|
+
amount = round(self.random.uniform(self.min_allowed_amount, self.max_allowed_amount), 2)
|
|
103
|
+
start_date = fake.date_this_decade(before_today=True, after_today=False)
|
|
104
|
+
date = start_date
|
|
105
|
+
|
|
106
|
+
most_expensive_amount = float("-inf")
|
|
107
|
+
|
|
108
|
+
most_expensive_duplicate_expense_number = None
|
|
109
|
+
oldest_duplicate_expense_number = None
|
|
110
|
+
only_expense_with_change_request = None
|
|
111
|
+
# id of expenses will be this id + their order in the creation
|
|
112
|
+
unique_id = str(int(self.unique_id.replace("-", ""), 16))[:10]
|
|
113
|
+
for i in range(self.total_expenses):
|
|
114
|
+
expense_number = f"EXP-{i}{unique_id }"
|
|
115
|
+
is_duplicate = i < self.num_duplicates
|
|
116
|
+
# set task sys_id to empty string if no change request is created
|
|
117
|
+
task_sys_id = ""
|
|
118
|
+
|
|
119
|
+
# Set a random date between start_date and today
|
|
120
|
+
if self.goal_type in ["base", "date"] and i > 0:
|
|
121
|
+
date = str(
|
|
122
|
+
fake.date_between(start_date=start_date + timedelta(1), end_date="today")
|
|
123
|
+
)
|
|
124
|
+
else:
|
|
125
|
+
date = start_date
|
|
126
|
+
if i == 0:
|
|
127
|
+
oldest_duplicate_expense_number = expense_number
|
|
128
|
+
|
|
129
|
+
# In the 'any' case, there are no change requests, all dates are the same and the prices are the same
|
|
130
|
+
if self.goal_type != "any":
|
|
131
|
+
amount = round(
|
|
132
|
+
self.random.uniform(self.min_allowed_amount, self.max_allowed_amount), 2
|
|
133
|
+
)
|
|
134
|
+
if is_duplicate and amount > most_expensive_amount:
|
|
135
|
+
most_expensive_amount = amount
|
|
136
|
+
most_expensive_duplicate_expense_number = expense_number
|
|
137
|
+
|
|
138
|
+
# Create a change request for the base case
|
|
139
|
+
if self.goal_type == "base" and i == 0:
|
|
140
|
+
task_sys_id, _ = create_change_request(
|
|
141
|
+
instance=self.instance,
|
|
142
|
+
user_sys_id=self._base_user_sysid,
|
|
143
|
+
hashtag=self.expense_hashtag,
|
|
144
|
+
impact=2,
|
|
145
|
+
risk=2,
|
|
146
|
+
random=self.random,
|
|
147
|
+
)
|
|
148
|
+
self.change_request_sysids.append(task_sys_id)
|
|
149
|
+
only_expense_with_change_request = expense_number
|
|
150
|
+
|
|
151
|
+
# Set the short description for the duplicate expenses; otherwise pass None, which will generate a random one
|
|
152
|
+
short_description = duplicate_short_description if i < self.num_duplicates else None
|
|
153
|
+
|
|
154
|
+
expense_sys_id, _ = create_expense_line(
|
|
155
|
+
instance=self.instance,
|
|
156
|
+
amount=amount,
|
|
157
|
+
number=expense_number,
|
|
158
|
+
date=str(date),
|
|
159
|
+
short_description=short_description,
|
|
160
|
+
expense_hashtag=self.expense_hashtag,
|
|
161
|
+
user_sys_id=self._base_user_sysid,
|
|
162
|
+
task_sys_id=task_sys_id,
|
|
163
|
+
)
|
|
164
|
+
self.expense_lines[expense_number] = (is_duplicate, expense_sys_id)
|
|
165
|
+
|
|
166
|
+
# keep the number of the expense that will be linked to the change request
|
|
167
|
+
if self.goal_type == "base":
|
|
168
|
+
self.expense_to_keep_number = only_expense_with_change_request
|
|
169
|
+
# keep the oldest expense
|
|
170
|
+
elif self.goal_type == "date":
|
|
171
|
+
self.expense_to_keep_number = oldest_duplicate_expense_number
|
|
172
|
+
elif self.goal_type == "amount":
|
|
173
|
+
self.expense_to_keep_number = most_expensive_duplicate_expense_number
|
|
174
|
+
else:
|
|
175
|
+
self.expense_to_keep_number = oldest_duplicate_expense_number
|
|
176
|
+
|
|
177
|
+
# As the task description redundant, we keep only the first one and skip the rest
|
|
178
|
+
skip_description = False
|
|
179
|
+
# Create the tasks to delete the extra expenses
|
|
180
|
+
for expense_number, (is_duplicate, expense_sys_id) in self.expense_lines.items():
|
|
181
|
+
if expense_number == self.expense_to_keep_number or not is_duplicate:
|
|
182
|
+
continue
|
|
183
|
+
self.tasks.append(
|
|
184
|
+
DeleteExpenseLineExpenseManagementTask(
|
|
185
|
+
instance=self.instance,
|
|
186
|
+
fixed_config={
|
|
187
|
+
"field_name": "number",
|
|
188
|
+
"field_value": f"{expense_number}",
|
|
189
|
+
},
|
|
190
|
+
is_validated=False,
|
|
191
|
+
used_in_level_2=True,
|
|
192
|
+
record_sys_id=expense_sys_id,
|
|
193
|
+
record_number=expense_number,
|
|
194
|
+
level=self.level,
|
|
195
|
+
skip_description=skip_description,
|
|
196
|
+
goal_type=self.goal_type,
|
|
197
|
+
)
|
|
198
|
+
)
|
|
199
|
+
skip_description = True
|
|
200
|
+
|
|
201
|
+
def validate(self, page: Page, chat_messages: list[str]) -> Tuple[float, bool, str, dict]:
|
|
202
|
+
expenses = table_api_call(
|
|
203
|
+
instance=self.instance,
|
|
204
|
+
table="fm_expense_line",
|
|
205
|
+
params={
|
|
206
|
+
"sysparm_query": f"short_descriptionLIKE{self.expense_hashtag}",
|
|
207
|
+
"sysparm_fields": "number,amount,sys_id",
|
|
208
|
+
},
|
|
209
|
+
)["result"]
|
|
210
|
+
# There should remain only one duplicate expense after the task is completed and the extra expenses should reamin
|
|
211
|
+
target_num_expenses = self.extra_expenses + 1
|
|
212
|
+
# Check that only one of the duplicated expenses exists and it is the right one
|
|
213
|
+
if len(expenses) != target_num_expenses:
|
|
214
|
+
return (
|
|
215
|
+
0,
|
|
216
|
+
False,
|
|
217
|
+
"",
|
|
218
|
+
{"message": "Wrong number of expenses."},
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
existing_expense_numbers = {expense["number"] for expense in expenses}
|
|
222
|
+
for expense_number, (is_duplicate, _) in self.expense_lines.items():
|
|
223
|
+
# Check that only one of the duplicated expenses exists and it is the right one
|
|
224
|
+
if expense_number == self.expense_to_keep_number:
|
|
225
|
+
if expense_number not in existing_expense_numbers:
|
|
226
|
+
return (
|
|
227
|
+
0,
|
|
228
|
+
False,
|
|
229
|
+
"",
|
|
230
|
+
{"message": "The expected duplicate to keep is missing."},
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
# Check that other duplicates have been deleted
|
|
234
|
+
elif is_duplicate and expense_number in existing_expense_numbers:
|
|
235
|
+
return (
|
|
236
|
+
0,
|
|
237
|
+
False,
|
|
238
|
+
"",
|
|
239
|
+
{"message": "An unexpected duplicate is present."},
|
|
240
|
+
)
|
|
241
|
+
# Check that the extra expenses have not been deleted
|
|
242
|
+
elif not is_duplicate and expense_number not in existing_expense_numbers:
|
|
243
|
+
return (
|
|
244
|
+
0,
|
|
245
|
+
False,
|
|
246
|
+
"",
|
|
247
|
+
{"message": "An extra expense has been deleted."},
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
# Validate final_l3 tasks
|
|
251
|
+
reward, done, message, info = super().validate(page, chat_messages)
|
|
252
|
+
return reward, done, message, info
|
|
253
|
+
|
|
254
|
+
def teardown(self) -> None:
|
|
255
|
+
for _, expense_sys_id in self.expense_lines.values():
|
|
256
|
+
record_exists = table_api_call(
|
|
257
|
+
instance=self.instance,
|
|
258
|
+
table="fm_expense_line",
|
|
259
|
+
params={"sysparm_query": f"sys_id={expense_sys_id}"},
|
|
260
|
+
)["result"]
|
|
261
|
+
if not record_exists:
|
|
262
|
+
continue
|
|
263
|
+
db_delete_from_table(
|
|
264
|
+
instance=self.instance,
|
|
265
|
+
table="fm_expense_line",
|
|
266
|
+
sys_id=expense_sys_id,
|
|
267
|
+
)
|
|
268
|
+
for change_request_sys_id in self.change_request_sysids:
|
|
269
|
+
record_exists = table_api_call(
|
|
270
|
+
instance=self.instance,
|
|
271
|
+
table="change_request",
|
|
272
|
+
params={"sysparm_query": f"sys_id={change_request_sys_id}"},
|
|
273
|
+
)["result"]
|
|
274
|
+
if not record_exists:
|
|
275
|
+
continue
|
|
276
|
+
db_delete_from_table(
|
|
277
|
+
instance=self.instance,
|
|
278
|
+
table="change_request",
|
|
279
|
+
sys_id=change_request_sys_id,
|
|
280
|
+
)
|
|
281
|
+
super().teardown()
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class BasicExpenseManagementSmallTask(ExpenseManagementTask, HumanEvalTask):
|
|
285
|
+
def __init__(
|
|
286
|
+
self,
|
|
287
|
+
seed: int,
|
|
288
|
+
instance: SNowInstance = None,
|
|
289
|
+
fixed_config: List[AbstractServiceNowTask] = None,
|
|
290
|
+
num_duplicates: int = 2,
|
|
291
|
+
extra_expenses: int = 2,
|
|
292
|
+
level: int = 2,
|
|
293
|
+
) -> None:
|
|
294
|
+
super().__init__(
|
|
295
|
+
seed=seed,
|
|
296
|
+
instance=instance,
|
|
297
|
+
fixed_config=fixed_config,
|
|
298
|
+
num_duplicates=num_duplicates,
|
|
299
|
+
extra_expenses=extra_expenses,
|
|
300
|
+
goal_type="base",
|
|
301
|
+
level=level,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class DateBasedExpenseManagementSmallTask(ExpenseManagementTask, HumanEvalTask):
|
|
306
|
+
def __init__(
|
|
307
|
+
self,
|
|
308
|
+
seed: int,
|
|
309
|
+
instance: SNowInstance = None,
|
|
310
|
+
fixed_config: List[AbstractServiceNowTask] = None,
|
|
311
|
+
num_duplicates: int = 2,
|
|
312
|
+
extra_expenses: int = 2,
|
|
313
|
+
level: int = 2,
|
|
314
|
+
) -> None:
|
|
315
|
+
super().__init__(
|
|
316
|
+
seed=seed,
|
|
317
|
+
instance=instance,
|
|
318
|
+
fixed_config=fixed_config,
|
|
319
|
+
num_duplicates=num_duplicates,
|
|
320
|
+
extra_expenses=extra_expenses,
|
|
321
|
+
goal_type="date",
|
|
322
|
+
level=level,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class AmountBasedExpenseManagementSmallTask(ExpenseManagementTask, HumanEvalTask):
|
|
327
|
+
def __init__(
|
|
328
|
+
self,
|
|
329
|
+
seed: int,
|
|
330
|
+
instance: SNowInstance = None,
|
|
331
|
+
fixed_config: List[AbstractServiceNowTask] = None,
|
|
332
|
+
num_duplicates: int = 2,
|
|
333
|
+
extra_expenses: int = 2,
|
|
334
|
+
level: int = 2,
|
|
335
|
+
) -> None:
|
|
336
|
+
super().__init__(
|
|
337
|
+
seed=seed,
|
|
338
|
+
instance=instance,
|
|
339
|
+
fixed_config=fixed_config,
|
|
340
|
+
num_duplicates=num_duplicates,
|
|
341
|
+
extra_expenses=extra_expenses,
|
|
342
|
+
goal_type="amount",
|
|
343
|
+
level=level,
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
class EasyExpenseManagementTask(ExpenseManagementTask):
|
|
348
|
+
def __init__(
|
|
349
|
+
self,
|
|
350
|
+
seed: int,
|
|
351
|
+
instance: SNowInstance = None,
|
|
352
|
+
fixed_config: List[AbstractServiceNowTask] = None,
|
|
353
|
+
num_duplicates: int = 2,
|
|
354
|
+
extra_expenses: int = 2,
|
|
355
|
+
level: int = 2,
|
|
356
|
+
) -> None:
|
|
357
|
+
super().__init__(
|
|
358
|
+
seed=seed,
|
|
359
|
+
instance=instance,
|
|
360
|
+
fixed_config=fixed_config,
|
|
361
|
+
num_duplicates=num_duplicates,
|
|
362
|
+
extra_expenses=extra_expenses,
|
|
363
|
+
goal_type="any",
|
|
364
|
+
level=level,
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
def validate(self, page: Page, chat_messages: list[str]) -> Tuple[float, bool, str, dict]:
|
|
368
|
+
expenses = table_api_call(
|
|
369
|
+
instance=self.instance,
|
|
370
|
+
table="fm_expense_line",
|
|
371
|
+
params={
|
|
372
|
+
"sysparm_query": f"short_descriptionLIKE{self.expense_hashtag}",
|
|
373
|
+
"sysparm_fields": "number,amount,sys_id",
|
|
374
|
+
},
|
|
375
|
+
)["result"]
|
|
376
|
+
# There should remain only one expense after the task is completed and the extra expenses should reamin
|
|
377
|
+
target_num_expenses = self.extra_expenses + 1
|
|
378
|
+
# Check that only one of the duplicated expenses exists and it is the right one
|
|
379
|
+
if len(expenses) != target_num_expenses:
|
|
380
|
+
return (
|
|
381
|
+
0,
|
|
382
|
+
False,
|
|
383
|
+
"",
|
|
384
|
+
{"message": "Wrong number of expenses."},
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
existing_expense_numbers = {expense["number"] for expense in expenses}
|
|
388
|
+
for expense_number, (is_duplicate, _) in self.expense_lines.items():
|
|
389
|
+
if not is_duplicate and expense_number not in existing_expense_numbers:
|
|
390
|
+
return (
|
|
391
|
+
0,
|
|
392
|
+
False,
|
|
393
|
+
"",
|
|
394
|
+
{"message": "An extra expense has been deleted."},
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
# Validate final_l3 tasks
|
|
398
|
+
reward, done, message, info = FilterAndDoTask.validate(self, page, chat_messages)
|
|
399
|
+
return reward, done, message, info
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
class EasyExpenseManagementSmallTask(EasyExpenseManagementTask, HumanEvalTask):
|
|
403
|
+
def __init__(
|
|
404
|
+
self,
|
|
405
|
+
seed: int,
|
|
406
|
+
instance: SNowInstance = None,
|
|
407
|
+
fixed_config: List[AbstractServiceNowTask] = None,
|
|
408
|
+
num_duplicates: int = 2,
|
|
409
|
+
extra_expenses: int = 2,
|
|
410
|
+
level: int = 2,
|
|
411
|
+
) -> None:
|
|
412
|
+
super().__init__(
|
|
413
|
+
seed=seed,
|
|
414
|
+
instance=instance,
|
|
415
|
+
fixed_config=fixed_config,
|
|
416
|
+
num_duplicates=num_duplicates,
|
|
417
|
+
extra_expenses=extra_expenses,
|
|
418
|
+
level=level,
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
class BasicExpenseManagementMediumTask(ExpenseManagementTask):
|
|
423
|
+
def __init__(
|
|
424
|
+
self,
|
|
425
|
+
seed: int,
|
|
426
|
+
instance: SNowInstance = None,
|
|
427
|
+
fixed_config: List[AbstractServiceNowTask] = None,
|
|
428
|
+
num_duplicates: int = 4,
|
|
429
|
+
extra_expenses: int = 4,
|
|
430
|
+
level: int = 2,
|
|
431
|
+
) -> None:
|
|
432
|
+
super().__init__(
|
|
433
|
+
seed=seed,
|
|
434
|
+
instance=instance,
|
|
435
|
+
fixed_config=fixed_config,
|
|
436
|
+
num_duplicates=num_duplicates,
|
|
437
|
+
extra_expenses=extra_expenses,
|
|
438
|
+
goal_type="base",
|
|
439
|
+
level=level,
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
class DateBasedExpenseManagementMediumTask(ExpenseManagementTask):
|
|
444
|
+
def __init__(
|
|
445
|
+
self,
|
|
446
|
+
seed: int,
|
|
447
|
+
instance: SNowInstance = None,
|
|
448
|
+
fixed_config: List[AbstractServiceNowTask] = None,
|
|
449
|
+
num_duplicates: int = 4,
|
|
450
|
+
extra_expenses: int = 4,
|
|
451
|
+
level: int = 2,
|
|
452
|
+
) -> None:
|
|
453
|
+
super().__init__(
|
|
454
|
+
seed=seed,
|
|
455
|
+
instance=instance,
|
|
456
|
+
fixed_config=fixed_config,
|
|
457
|
+
num_duplicates=num_duplicates,
|
|
458
|
+
extra_expenses=extra_expenses,
|
|
459
|
+
goal_type="date",
|
|
460
|
+
level=level,
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
class AmountBasedExpenseManagementMediumTask(ExpenseManagementTask):
|
|
465
|
+
def __init__(
|
|
466
|
+
self,
|
|
467
|
+
seed: int,
|
|
468
|
+
instance: SNowInstance = None,
|
|
469
|
+
fixed_config: List[AbstractServiceNowTask] = None,
|
|
470
|
+
num_duplicates: int = 4,
|
|
471
|
+
extra_expenses: int = 4,
|
|
472
|
+
level: int = 2,
|
|
473
|
+
) -> None:
|
|
474
|
+
super().__init__(
|
|
475
|
+
seed=seed,
|
|
476
|
+
instance=instance,
|
|
477
|
+
fixed_config=fixed_config,
|
|
478
|
+
num_duplicates=num_duplicates,
|
|
479
|
+
extra_expenses=extra_expenses,
|
|
480
|
+
goal_type="amount",
|
|
481
|
+
level=level,
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
class EasyExpenseManagementMediumTask(EasyExpenseManagementTask):
|
|
486
|
+
def __init__(
|
|
487
|
+
self,
|
|
488
|
+
seed: int,
|
|
489
|
+
instance: SNowInstance = None,
|
|
490
|
+
fixed_config: List[AbstractServiceNowTask] = None,
|
|
491
|
+
num_duplicates: int = 4,
|
|
492
|
+
extra_expenses: int = 4,
|
|
493
|
+
level: int = 2,
|
|
494
|
+
) -> None:
|
|
495
|
+
super().__init__(
|
|
496
|
+
seed=seed,
|
|
497
|
+
instance=instance,
|
|
498
|
+
fixed_config=fixed_config,
|
|
499
|
+
num_duplicates=num_duplicates,
|
|
500
|
+
extra_expenses=extra_expenses,
|
|
501
|
+
level=level,
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
class BasicExpenseManagementLargeTask(ExpenseManagementTask):
|
|
506
|
+
def __init__(
|
|
507
|
+
self,
|
|
508
|
+
seed: int,
|
|
509
|
+
instance: SNowInstance = None,
|
|
510
|
+
fixed_config: List[AbstractServiceNowTask] = None,
|
|
511
|
+
num_duplicates: int = 6,
|
|
512
|
+
extra_expenses: int = 6,
|
|
513
|
+
level: int = 2,
|
|
514
|
+
) -> None:
|
|
515
|
+
super().__init__(
|
|
516
|
+
seed=seed,
|
|
517
|
+
instance=instance,
|
|
518
|
+
fixed_config=fixed_config,
|
|
519
|
+
num_duplicates=num_duplicates,
|
|
520
|
+
extra_expenses=extra_expenses,
|
|
521
|
+
goal_type="base",
|
|
522
|
+
level=level,
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
class DateBasedExpenseManagementLargeTask(ExpenseManagementTask):
|
|
527
|
+
def __init__(
|
|
528
|
+
self,
|
|
529
|
+
seed: int,
|
|
530
|
+
instance: SNowInstance = None,
|
|
531
|
+
fixed_config: List[AbstractServiceNowTask] = None,
|
|
532
|
+
num_duplicates: int = 6,
|
|
533
|
+
extra_expenses: int = 6,
|
|
534
|
+
level: int = 2,
|
|
535
|
+
) -> None:
|
|
536
|
+
super().__init__(
|
|
537
|
+
seed=seed,
|
|
538
|
+
instance=instance,
|
|
539
|
+
fixed_config=fixed_config,
|
|
540
|
+
num_duplicates=num_duplicates,
|
|
541
|
+
extra_expenses=extra_expenses,
|
|
542
|
+
goal_type="date",
|
|
543
|
+
level=level,
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
class AmountBasedExpenseManagementLargeTask(ExpenseManagementTask):
|
|
548
|
+
def __init__(
|
|
549
|
+
self,
|
|
550
|
+
seed: int,
|
|
551
|
+
instance: SNowInstance = None,
|
|
552
|
+
fixed_config: List[AbstractServiceNowTask] = None,
|
|
553
|
+
num_duplicates: int = 6,
|
|
554
|
+
extra_expenses: int = 6,
|
|
555
|
+
level: int = 2,
|
|
556
|
+
) -> None:
|
|
557
|
+
super().__init__(
|
|
558
|
+
seed=seed,
|
|
559
|
+
instance=instance,
|
|
560
|
+
fixed_config=fixed_config,
|
|
561
|
+
num_duplicates=num_duplicates,
|
|
562
|
+
extra_expenses=extra_expenses,
|
|
563
|
+
goal_type="amount",
|
|
564
|
+
level=level,
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
class EasyExpenseManagementLargeTask(EasyExpenseManagementTask):
|
|
569
|
+
def __init__(
|
|
570
|
+
self,
|
|
571
|
+
seed: int,
|
|
572
|
+
instance: SNowInstance = None,
|
|
573
|
+
fixed_config: List[AbstractServiceNowTask] = None,
|
|
574
|
+
num_duplicates: int = 6,
|
|
575
|
+
extra_expenses: int = 6,
|
|
576
|
+
level: int = 2,
|
|
577
|
+
) -> None:
|
|
578
|
+
super().__init__(
|
|
579
|
+
seed=seed,
|
|
580
|
+
instance=instance,
|
|
581
|
+
fixed_config=fixed_config,
|
|
582
|
+
num_duplicates=num_duplicates,
|
|
583
|
+
extra_expenses=extra_expenses,
|
|
584
|
+
level=level,
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
local_vars = locals().copy()
|
|
589
|
+
|
|
590
|
+
__TASKS__ = [
|
|
591
|
+
var
|
|
592
|
+
for var in local_vars.values()
|
|
593
|
+
if isinstance(var, type)
|
|
594
|
+
and issubclass(var, FilterAndDoTask)
|
|
595
|
+
and var is not FilterAndDoTask
|
|
596
|
+
and var is not ExpenseManagementTask
|
|
597
|
+
and var is not EasyExpenseManagementTask
|
|
598
|
+
]
|