indico_toolkit 6.14.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.
Files changed (83) hide show
  1. indico_toolkit/__init__.py +24 -0
  2. indico_toolkit/association/__init__.py +15 -0
  3. indico_toolkit/association/association.py +65 -0
  4. indico_toolkit/association/extracted_tokens.py +106 -0
  5. indico_toolkit/association/line_items.py +189 -0
  6. indico_toolkit/association/positioning.py +301 -0
  7. indico_toolkit/association/splitting.py +41 -0
  8. indico_toolkit/auto_populate/__init__.py +3 -0
  9. indico_toolkit/auto_populate/populator.py +362 -0
  10. indico_toolkit/auto_populate/types.py +66 -0
  11. indico_toolkit/auto_review/__init__.py +6 -0
  12. indico_toolkit/auto_review/auto_review_functions.py +123 -0
  13. indico_toolkit/auto_review/auto_reviewer.py +62 -0
  14. indico_toolkit/client.py +35 -0
  15. indico_toolkit/errors.py +32 -0
  16. indico_toolkit/etloutput/__init__.py +210 -0
  17. indico_toolkit/etloutput/cell.py +47 -0
  18. indico_toolkit/etloutput/errors.py +16 -0
  19. indico_toolkit/etloutput/etloutput.py +110 -0
  20. indico_toolkit/etloutput/range.py +30 -0
  21. indico_toolkit/etloutput/table.py +44 -0
  22. indico_toolkit/etloutput/token.py +25 -0
  23. indico_toolkit/indico_wrapper/__init__.py +15 -0
  24. indico_toolkit/indico_wrapper/dataset.py +114 -0
  25. indico_toolkit/indico_wrapper/doc_extraction.py +79 -0
  26. indico_toolkit/indico_wrapper/download.py +193 -0
  27. indico_toolkit/indico_wrapper/indico_wrapper.py +119 -0
  28. indico_toolkit/indico_wrapper/modelop.py +200 -0
  29. indico_toolkit/indico_wrapper/reviewer.py +111 -0
  30. indico_toolkit/indico_wrapper/workflow.py +231 -0
  31. indico_toolkit/metrics/__init__.py +11 -0
  32. indico_toolkit/metrics/compare_ground_truth.py +123 -0
  33. indico_toolkit/metrics/compare_models.py +180 -0
  34. indico_toolkit/metrics/metrics.py +373 -0
  35. indico_toolkit/metrics/plotting.py +102 -0
  36. indico_toolkit/ocr/__init__.py +9 -0
  37. indico_toolkit/ocr/customocr_object.py +42 -0
  38. indico_toolkit/ocr/ondoc_object.py +93 -0
  39. indico_toolkit/ocr/standard_object.py +54 -0
  40. indico_toolkit/pipelines/__init__.py +3 -0
  41. indico_toolkit/pipelines/file_processing.py +157 -0
  42. indico_toolkit/polling/__init__.py +8 -0
  43. indico_toolkit/polling/autoreview.py +204 -0
  44. indico_toolkit/polling/downstream.py +145 -0
  45. indico_toolkit/polling/queries.py +68 -0
  46. indico_toolkit/py.typed +0 -0
  47. indico_toolkit/results/__init__.py +104 -0
  48. indico_toolkit/results/document.py +78 -0
  49. indico_toolkit/results/errors.py +4 -0
  50. indico_toolkit/results/model.py +60 -0
  51. indico_toolkit/results/normalization.py +159 -0
  52. indico_toolkit/results/predictionlist.py +395 -0
  53. indico_toolkit/results/predictions/__init__.py +88 -0
  54. indico_toolkit/results/predictions/box.py +58 -0
  55. indico_toolkit/results/predictions/citation.py +47 -0
  56. indico_toolkit/results/predictions/classification.py +50 -0
  57. indico_toolkit/results/predictions/documentextraction.py +160 -0
  58. indico_toolkit/results/predictions/extraction.py +35 -0
  59. indico_toolkit/results/predictions/formextraction.py +121 -0
  60. indico_toolkit/results/predictions/group.py +29 -0
  61. indico_toolkit/results/predictions/prediction.py +41 -0
  62. indico_toolkit/results/predictions/span.py +43 -0
  63. indico_toolkit/results/predictions/summarization.py +103 -0
  64. indico_toolkit/results/predictions/unbundling.py +51 -0
  65. indico_toolkit/results/result.py +152 -0
  66. indico_toolkit/results/review.py +32 -0
  67. indico_toolkit/results/utilities.py +73 -0
  68. indico_toolkit/retry.py +94 -0
  69. indico_toolkit/snapshots/__init__.py +3 -0
  70. indico_toolkit/snapshots/snapshot.py +340 -0
  71. indico_toolkit/structure/__init__.py +3 -0
  72. indico_toolkit/structure/create_structure.py +232 -0
  73. indico_toolkit/structure/queries.py +119 -0
  74. indico_toolkit/structure/utils.py +16 -0
  75. indico_toolkit/types/__init__.py +12 -0
  76. indico_toolkit/types/classification.py +92 -0
  77. indico_toolkit/types/extractions.py +227 -0
  78. indico_toolkit/types/predictions.py +28 -0
  79. indico_toolkit/types/workflow_object.py +133 -0
  80. indico_toolkit-6.14.0.dist-info/LICENSE +21 -0
  81. indico_toolkit-6.14.0.dist-info/METADATA +144 -0
  82. indico_toolkit-6.14.0.dist-info/RECORD +83 -0
  83. indico_toolkit-6.14.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,362 @@
1
+ import dataclasses
2
+ import time
3
+ from json import loads
4
+ from os import PathLike
5
+ from pathlib import Path
6
+ from typing import Dict, List, Tuple, Union
7
+
8
+ from indico import IndicoClient
9
+ from indico.queries import (
10
+ CreateExport,
11
+ DownloadExport,
12
+ GetDataset,
13
+ GetModelGroup,
14
+ )
15
+ from indico.types import Workflow
16
+
17
+ from ..errors import ToolkitPopulationError
18
+ from ..structure.create_structure import Structure
19
+ from .types import (
20
+ Example,
21
+ ExampleList,
22
+ LabelInput,
23
+ LabelInst,
24
+ TokenSpanInput,
25
+ )
26
+
27
+
28
+ class AutoPopulator:
29
+ def __init__(self, client: IndicoClient):
30
+ """
31
+ Module for manipulating and creating new workflows and teach tasks.
32
+
33
+ Args:
34
+ client (IndicoClient): instantiated Indico Client
35
+ """
36
+ self.client = client
37
+ self.structure = Structure(client)
38
+ self._exceptions = []
39
+
40
+ def create_auto_classification_workflow(
41
+ self,
42
+ directory_path: Union[str, PathLike[str]],
43
+ dataset_name: str,
44
+ workflow_name: str,
45
+ teach_task_name: str,
46
+ accepted_types: Tuple[str, ...] = (
47
+ "csv",
48
+ "doc",
49
+ "docx",
50
+ "eml",
51
+ "jpeg",
52
+ "jpg",
53
+ "msg",
54
+ "pdf",
55
+ "png",
56
+ "pptx",
57
+ "rtf",
58
+ "svg",
59
+ "tif",
60
+ "tiff",
61
+ "txt",
62
+ "xls",
63
+ "xlsx",
64
+ ),
65
+ ) -> Workflow:
66
+ """
67
+ Label and train a model based on a directory structure or existing teach task.
68
+ You should have a base directory containing sub directories where each
69
+ directory contains a unique file type and only that file type.
70
+
71
+ Example:
72
+ base_directory/
73
+ base_directory/invoices/ -> folder containing only invoices
74
+ base_directory/disclosures/ -> folder containing only disclosures
75
+ etc. etc.
76
+ Args:
77
+ directory_path (str): Path to a directory containing your filepath structure
78
+ dataset_name (str): Name of created dataset
79
+ worlflow_name (str): Name of created workflow
80
+ teach_task_name (str): Name of created teach task
81
+ accepted_types (Tuple[str], optional): List of accepted file types to search
82
+ Returns:
83
+ Workflow: a Workflow object representation of the newly created workflow
84
+ """
85
+
86
+ def valid_file(file: Path) -> bool:
87
+ return (
88
+ file.is_file() and file.suffix.strip(".").casefold() in accepted_types
89
+ )
90
+
91
+ folder = Path(directory_path)
92
+ files = list(filter(valid_file, folder.glob("*/*")))
93
+ classes = list(set(file.parent.name for file in files))
94
+ labeled_files = {file.name: [{"label": file.parent.name}] for file in files}
95
+
96
+ if len(classes) < 2:
97
+ raise ToolkitPopulationError(
98
+ "You must have documents in at least 2 directories, "
99
+ f"you only have {len(classes)}"
100
+ )
101
+
102
+ # Upload files to a new dataset.
103
+ dataset = self.structure.create_dataset(
104
+ dataset_name=dataset_name,
105
+ files_to_upload=files,
106
+ read_api=True,
107
+ single_column=False,
108
+ auto_rotate=False,
109
+ upscale_images=True,
110
+ languages=["ENG"],
111
+ )
112
+
113
+ # Create a new workflow with classification model.
114
+ workflow = self.structure.create_workflow(workflow_name, dataset.id)
115
+ workflow = self.structure.add_teach_task(
116
+ task_name=teach_task_name,
117
+ labelset_name=f"{teach_task_name}_labelset",
118
+ target_names=classes,
119
+ dataset_id=dataset.id,
120
+ workflow_id=workflow.id,
121
+ model_type="classification",
122
+ )
123
+ teach_task_id = workflow.components[-1].model_group.questionnaire_id
124
+ labelset_id, model_group_id, label_map = self._get_teach_task_details(
125
+ teach_task_id
126
+ )
127
+
128
+ labels = self.get_labels_by_filename(model_group_id, labeled_files, label_map)
129
+ self.structure.label_teach_task(
130
+ label_set_id=labelset_id,
131
+ labels=list(map(dataclasses.asdict, labels)),
132
+ model_group_id=model_group_id,
133
+ )
134
+
135
+ return workflow
136
+
137
+ def copy_teach_task(
138
+ self,
139
+ dataset_id: int,
140
+ teach_task_id: int,
141
+ workflow_name: str,
142
+ data_column: str = "document",
143
+ rename_labels: Dict[str, str] = None,
144
+ remove_labels: List[str] = None,
145
+ ) -> Workflow:
146
+ """
147
+ Create duplicate teach task in same Indico platform.
148
+
149
+ Note: Does not work with datasets created with a snapshot
150
+
151
+ Args:
152
+ dataset_id (int): The dataset id of the dataset you wish to copy
153
+ teach_task_id (int): The teach task id of the corresponding teach task to
154
+ the dataset
155
+ workflow_name (string): The name of the newly created workflow
156
+ data_column_id (str, optional): The datacolumn id of the corresponding
157
+ dataset. Defaults to 'document'
158
+ rename_labels (dict, optional): Dictionary in format
159
+ {old_label_name : new_label_name}
160
+ remove_labels (list, optional): List of labels to remove from old teach task
161
+
162
+ Returns:
163
+ Workflow: a Workflow object representation of the newly created workflow
164
+ """
165
+ dataset = self.client.call(GetDataset(dataset_id))
166
+ (
167
+ old_labelset_id,
168
+ old_model_group_id,
169
+ old_target_name_map,
170
+ ) = self._get_teach_task_details(teach_task_id=teach_task_id)
171
+ # get dataset snapshot
172
+ export = self.client.call(
173
+ CreateExport(dataset_id=dataset.id, labelset_id=old_labelset_id, wait=True)
174
+ )
175
+ csv = self.client.call(DownloadExport(export.id))
176
+ print("Obtained snapshot")
177
+
178
+ # create workflow
179
+ workflow = self.structure.create_workflow(
180
+ name=workflow_name, dataset_id=dataset_id
181
+ )
182
+ time.sleep(2)
183
+ print("Created workflow")
184
+ old_model_group = self.client.call(
185
+ GetModelGroup(id=old_model_group_id, wait=True)
186
+ )
187
+ model_type = old_model_group.task_type.lower()
188
+ # Create new teach task
189
+ workflow = self.structure.add_teach_task(
190
+ task_name=workflow_name,
191
+ labelset_name=workflow_name,
192
+ target_names=list(old_target_name_map.keys()),
193
+ dataset_id=dataset.id,
194
+ workflow_id=workflow.id,
195
+ model_type=model_type,
196
+ data_column=data_column,
197
+ )
198
+ (
199
+ new_labelset_id,
200
+ new_model_group_id,
201
+ new_target_name_map,
202
+ ) = self._get_teach_task_details(
203
+ workflow.components[-1].model_group.questionnaire_id
204
+ )
205
+ # Get file_to_targets from export CSV
206
+ file_to_targets = {}
207
+ for _, row in csv.iterrows():
208
+ # Check for NaN filled rows
209
+ if isinstance(row[2], float):
210
+ continue
211
+ old_example_id = row[0]
212
+ old_examples = self._get_example_list(old_model_group_id)
213
+ targets_list = loads(row[2])["targets"]
214
+ file_to_targets[old_examples.get_example(old_example_id).data_file_name] = (
215
+ targets_list
216
+ )
217
+ labels = self.get_labels_by_filename(
218
+ new_model_group_id,
219
+ file_to_targets,
220
+ new_target_name_map,
221
+ rename_labels,
222
+ remove_labels,
223
+ )
224
+ # Label new teach task
225
+ result = self.structure.label_teach_task(
226
+ label_set_id=new_labelset_id,
227
+ labels=[dataclasses.asdict(label) for label in labels],
228
+ model_group_id=new_model_group_id,
229
+ )
230
+ if not result["submitLabelsV2"]["success"]:
231
+ raise ToolkitPopulationError("Error: Failed to submit labels")
232
+ return workflow
233
+
234
+ def inject_labels_into_teach_task(
235
+ self,
236
+ workflow_id: int,
237
+ teach_task_id: int,
238
+ file_to_targets: dict,
239
+ rename_labels: Dict[str, str] = None,
240
+ remove_labels: List[str] = None,
241
+ ):
242
+ """
243
+ Add label data into existing teach task
244
+
245
+ Args:
246
+ workflow_id (int): Id of the workflow you wish to add labels to
247
+ teach_task_id (int): Id of the corresponding teach task to the workflow
248
+ file_to_targets (dict): mapping of filenames to target label data
249
+ rename_labels (dict, optional): Dictionary in format
250
+ {old_label_name : new_label_name}
251
+ remove_labels (list, optional): List of labels to remove from old teach task
252
+ """
253
+ (
254
+ labelset_id,
255
+ model_group_id,
256
+ target_name_map,
257
+ ) = self._get_teach_task_details(teach_task_id)
258
+ labels = self.get_labels_by_filename(
259
+ model_group_id,
260
+ file_to_targets,
261
+ target_name_map,
262
+ rename_labels,
263
+ remove_labels,
264
+ )
265
+ # Label new teach task
266
+ result = self.structure.label_teach_task(
267
+ label_set_id=labelset_id,
268
+ labels=[dataclasses.asdict(label) for label in labels],
269
+ model_group_id=model_group_id,
270
+ )
271
+ if not result["submitLabelsV2"]["success"]:
272
+ raise ToolkitPopulationError("Error: Failed to submit labels")
273
+
274
+ def get_labels_by_filename(
275
+ self,
276
+ model_group_id: int,
277
+ file_to_targets: dict,
278
+ target_name_map: dict,
279
+ rename_labels: Dict[str, str] = None,
280
+ remove_labels: List[str] = None,
281
+ ) -> List[LabelInput]:
282
+ """
283
+ Args:
284
+ model_group_id (int): ID of the model group to be labeled
285
+ file_to_targets (dict): mapping in the format {filename : targets_list}
286
+ target_name_map (dict): mapping of field name to corresponding target ID
287
+ rename_labels (dict, optional): Dictionary in format
288
+ {old_label_name : new_label_name}
289
+ remove_labels (list, optional): List of labels to remove from old teach task
290
+
291
+ Returns:
292
+ A list of LabelInput to be ingested by the platform via submitLabelsV2
293
+ """
294
+ labels = []
295
+ # Retrieve examples and match against filename
296
+ examples = self._get_example_list(model_group_id)
297
+
298
+ for filename, targets_list in file_to_targets.items():
299
+ if rename_labels or remove_labels:
300
+ targets_list = self._edit_labels(
301
+ targets_list, rename_labels, remove_labels
302
+ )
303
+ targets_list = self._convert_label(targets_list, target_name_map)
304
+ example_id = examples.get_example_id(filename)
305
+ if example_id:
306
+ labels.append(LabelInput(example_id, targets_list))
307
+ return labels
308
+
309
+ def _edit_labels(
310
+ self,
311
+ targets_list: List[dict],
312
+ rename_labels: Dict[str, str],
313
+ remove_labels: List[str],
314
+ ):
315
+ new_targets_list = []
316
+ for target in targets_list:
317
+ if remove_labels and target["label"] not in remove_labels:
318
+ if rename_labels and rename_labels.get(target["label"]):
319
+ target["label"] = rename_labels[target["label"]]
320
+ new_targets_list.append(target)
321
+ return new_targets_list
322
+
323
+ def _convert_label(
324
+ self, targets_list: List[dict], target_name_map: dict
325
+ ) -> List[LabelInst]:
326
+ updated_labels = []
327
+ for target in targets_list:
328
+ updated_label = LabelInst(target_name_map[target["label"]])
329
+ if target.get("spans"):
330
+ updated_spans = [
331
+ TokenSpanInput(span["start"], span["end"], span["page_num"])
332
+ for span in target["spans"]
333
+ ]
334
+ updated_label.spans = updated_spans
335
+ updated_labels.append(updated_label)
336
+ return updated_labels
337
+
338
+ def _get_teach_task_details(self, teach_task_id: int):
339
+ teach_task_details = self.structure.get_teach_details(
340
+ teach_task_id=teach_task_id
341
+ )
342
+ labelset_id = teach_task_details["questionnaire"]["question"]["labelset"]["id"]
343
+ model_group_id = teach_task_details["questionnaire"]["question"]["modelGroupId"]
344
+ target_names = teach_task_details["questionnaire"]["question"]["labelset"][
345
+ "targetNames"
346
+ ]
347
+ target_name_map = {}
348
+ for target in target_names:
349
+ target_name_map[target["name"]] = target["id"]
350
+ return labelset_id, model_group_id, target_name_map
351
+
352
+ def _get_example_list(self, model_group_id: int, limit=1000):
353
+ examples = self.structure.get_example_ids(
354
+ model_group_id=model_group_id, limit=limit
355
+ )
356
+ examples = ExampleList(
357
+ examples=[
358
+ Example(i["id"], i["datafile"]["name"])
359
+ for i in examples["modelGroup"]["pagedExamples"]["examples"]
360
+ ]
361
+ )
362
+ return examples
@@ -0,0 +1,66 @@
1
+ from dataclasses import dataclass
2
+ from typing import List
3
+
4
+
5
+ @dataclass
6
+ class Example:
7
+ id: int
8
+ data_file_name: str
9
+
10
+
11
+ class ExampleList:
12
+ def __init__(self, examples: List[Example]):
13
+ self.examples = examples
14
+
15
+ def get_example(self, example_id: int) -> Example:
16
+ """
17
+ Returns example with matching example_id. If no matching example id found,
18
+ return None.
19
+ """
20
+ for example in self.examples:
21
+ if example.id == example_id:
22
+ return example
23
+ return None
24
+
25
+ def get_example_id(self, example_data_file_name: str) -> int:
26
+ """
27
+ Returns id for a specific example with the same name as example_data_file_name.
28
+ If no matching example found, return None. Assumes no duplicate filenames in
29
+ dataset
30
+ """
31
+ for example in self.examples:
32
+ if example.data_file_name == example_data_file_name:
33
+ return example.id
34
+ return None
35
+
36
+
37
+ @dataclass
38
+ class TokenSpanInput:
39
+ start: int
40
+ end: int
41
+ pageNum: int
42
+
43
+
44
+ @dataclass
45
+ class SpatialSpanInput:
46
+ top: int
47
+ bottom: int
48
+ left: int
49
+ right: int
50
+ pageNum: int
51
+
52
+
53
+ @dataclass
54
+ class LabelInst:
55
+ clsId: int
56
+ spans: List[TokenSpanInput] = None
57
+ bounds: List[SpatialSpanInput] = None
58
+
59
+
60
+ @dataclass
61
+ class LabelInput:
62
+ exampleId: int
63
+ targets: List[LabelInst]
64
+ rejected: bool = None
65
+ override: bool = None
66
+ partial: bool = None
@@ -0,0 +1,6 @@
1
+ from .auto_reviewer import AutoReviewer, AutoReviewFunction
2
+
3
+ __all__ = (
4
+ "AutoReviewer",
5
+ "AutoReviewFunction",
6
+ )
@@ -0,0 +1,123 @@
1
+ from collections import defaultdict
2
+ from typing import List
3
+
4
+ ACCEPTED = "accepted"
5
+ REJECTED = "rejected"
6
+
7
+
8
+ def empty_or_matching_label(prediction: dict, labels: list) -> bool:
9
+ """
10
+ Returns:
11
+ bool: if labels list is empty or prediction label matches a label in the list
12
+ """
13
+ if len(labels) == 0 or prediction["label"] in labels:
14
+ return True
15
+ else:
16
+ return False
17
+
18
+
19
+ def reject_by_confidence(
20
+ predictions: List[dict], labels: List[str] = [], conf_threshold=0.50
21
+ ) -> List[dict]:
22
+ """
23
+ Rejects predictions below a given confidence threshold
24
+ Returns:
25
+ predictions List[dict]: all predictions
26
+ """
27
+ for prediction in predictions:
28
+ if REJECTED not in prediction:
29
+ if not empty_or_matching_label(prediction, labels):
30
+ continue
31
+ if prediction["confidence"][prediction["label"]] < conf_threshold:
32
+ prediction[REJECTED] = True
33
+ prediction.pop(ACCEPTED, None)
34
+ return predictions
35
+
36
+
37
+ def remove_by_confidence(
38
+ predictions: List[dict], labels: List[str] = [], conf_threshold=0.50
39
+ ) -> List[dict]:
40
+ """
41
+ Removes predictions below a given confidence threshold
42
+ Returns:
43
+ updated_predictions List[dict]: predictions above conf_threshold
44
+ """
45
+ for prediction in predictions:
46
+ if REJECTED not in prediction:
47
+ if not empty_or_matching_label(prediction, labels):
48
+ continue
49
+ if prediction["confidence"][prediction["label"]] < conf_threshold:
50
+ predictions.remove(prediction)
51
+ return predictions
52
+
53
+
54
+ def accept_by_confidence(
55
+ predictions: List[dict], labels: List[str] = [], conf_threshold=0.98
56
+ ) -> List[dict]:
57
+ """
58
+ Accepts predictions above a given confidence threshold
59
+ Returns:
60
+ predictions List[dict]: all predictions
61
+ """
62
+ for prediction in predictions:
63
+ if REJECTED not in prediction:
64
+ if not empty_or_matching_label(prediction, labels):
65
+ continue
66
+ if prediction["confidence"][prediction["label"]] > conf_threshold:
67
+ prediction[ACCEPTED] = True
68
+ return predictions
69
+
70
+
71
+ def accept_by_all_match_and_confidence(
72
+ predictions: List[dict], labels: List[str] = [], conf_threshold=0.98
73
+ ):
74
+ """
75
+ Accepts all predictions for a class if all their values are the same,
76
+ and all confidence scores are above a given confidence threshold
77
+ Returns:
78
+ predictions List[dict]: all predictions
79
+ """
80
+ pred_map = defaultdict(set)
81
+ for pred in predictions:
82
+ if REJECTED not in pred:
83
+ if not empty_or_matching_label(pred, labels):
84
+ continue
85
+ if pred["confidence"][pred["label"]] > conf_threshold:
86
+ pred_map[pred["label"]].add(pred["text"])
87
+ else:
88
+ pred_map[pred["label"]].update((1, 2))
89
+ for pred in predictions:
90
+ if pred["label"] in pred_map:
91
+ if len(pred_map[pred["label"]]) == 1:
92
+ pred[ACCEPTED] = True
93
+ return predictions
94
+
95
+
96
+ def reject_by_min_character_length(
97
+ predictions: List[dict], labels: List[str] = [], min_length_threshold=3
98
+ ) -> List[dict]:
99
+ """
100
+ Rejects predictions shorter than a given minimum length
101
+ Returns:
102
+ predictions List[dict]: all predictions
103
+ """
104
+ for prediction in predictions:
105
+ if empty_or_matching_label(prediction, labels):
106
+ if len(prediction["text"]) < min_length_threshold:
107
+ prediction[REJECTED] = True
108
+ return predictions
109
+
110
+
111
+ def reject_by_max_character_length(
112
+ predictions: List[dict], labels: List[str] = [], max_length_threshold=10
113
+ ) -> List[dict]:
114
+ """
115
+ Rejects predictions longer than a given maximum length
116
+ Returns:
117
+ predictions List[dict]: all prediction
118
+ """
119
+ for prediction in predictions:
120
+ if empty_or_matching_label(prediction, labels):
121
+ if len(prediction["text"]) > max_length_threshold:
122
+ prediction[REJECTED] = True
123
+ return predictions
@@ -0,0 +1,62 @@
1
+ from typing import Callable, Dict, List
2
+
3
+
4
+ class AutoReviewFunction:
5
+ """
6
+ Class for hosting functions to manipulate predictions before sending to
7
+ auto review
8
+
9
+ Args:
10
+ function (Callable): method to be invoked when applying reviews.
11
+ The Callable must have the following arguments in the following order:
12
+ predictions (List[dict]),
13
+ labels (List[str]),
14
+ **kwargs,
15
+ labels (List[str]): list of labels to invoke method on. Defaults to all labels
16
+ kwargs (Dict[str, str]): dictionary containing additional arguments needed in
17
+ calling function
18
+ """
19
+
20
+ def __init__(
21
+ self,
22
+ function: Callable,
23
+ labels: List[str] = [],
24
+ kwargs: Dict[str, str] = {},
25
+ ):
26
+ self.function = function
27
+ self.labels = labels
28
+ self.kwargs = kwargs
29
+
30
+ def apply(self, predictions: List[dict]):
31
+ if predictions and not self.labels:
32
+ self.labels = list(set([pred["label"] for pred in predictions]))
33
+ return self.function(predictions, self.labels, **self.kwargs)
34
+
35
+
36
+ class AutoReviewer:
37
+ """
38
+ Class for programatically reviewing workflow predictions
39
+
40
+ Example Usage:
41
+
42
+ reviewer = AutoReviewer(
43
+ predictions, functions
44
+ )
45
+
46
+ # Get your updated predictions
47
+ updated_predictions: List[dict] = reviewer.apply_reviews()
48
+ """
49
+
50
+ def __init__(
51
+ self,
52
+ predictions: List[dict],
53
+ functions: List[AutoReviewFunction] = [],
54
+ ):
55
+ self.predictions = predictions
56
+ self.updated_predictions = predictions
57
+ self.functions = functions
58
+
59
+ def apply_reviews(self) -> list:
60
+ for function in self.functions:
61
+ self.updated_predictions = function.apply(self.updated_predictions)
62
+ return self.updated_predictions
@@ -0,0 +1,35 @@
1
+ from typing import Any
2
+
3
+ from indico import IndicoClient, IndicoConfig
4
+ from indico.errors import IndicoAuthenticationFailed, IndicoRequestError
5
+
6
+ from .errors import ToolkitAuthError
7
+ from .retry import retry
8
+
9
+
10
+ @retry(IndicoRequestError, ConnectionError)
11
+ def create_client(
12
+ host: str,
13
+ api_token_path: "str | None" = None,
14
+ api_token_string: "str | None" = None,
15
+ verify_ssl: bool = True,
16
+ **kwargs: Any,
17
+ ) -> IndicoClient:
18
+ """
19
+ Instantiate your Indico API client.
20
+ Specify either the path to your token or the token string itself.
21
+ """
22
+ config = IndicoConfig(
23
+ host=host,
24
+ api_token_path=api_token_path,
25
+ api_token=api_token_string,
26
+ verify_ssl=verify_ssl,
27
+ **kwargs,
28
+ )
29
+ try:
30
+ return IndicoClient(config)
31
+ except IndicoAuthenticationFailed as e:
32
+ raise ToolkitAuthError(
33
+ f"{e}\nEnsure that you are using your most recently downloaded token with "
34
+ "the correct host URL"
35
+ )
@@ -0,0 +1,32 @@
1
+ class ToolkitError(Exception):
2
+ pass
3
+
4
+
5
+ class ToolkitAuthError(ToolkitError):
6
+ def __init__(self, msg: str):
7
+ super().__init__(msg)
8
+
9
+
10
+ class ToolkitStatusError(ToolkitError):
11
+ def __init__(self, msg: str):
12
+ super().__init__(msg)
13
+
14
+
15
+ class ToolkitInputError(ToolkitError):
16
+ def __init__(self, msg: str):
17
+ super().__init__(msg)
18
+
19
+
20
+ class ToolkitInstantiationError(ToolkitError):
21
+ def __init__(self, msg: str):
22
+ super().__init__(msg)
23
+
24
+
25
+ class ToolkitPopulationError(ToolkitError):
26
+ def __init__(self, msg: str):
27
+ super().__init__(msg)
28
+
29
+
30
+ class ToolkitStaggeredLoopError(ToolkitError):
31
+ def __init__(self, msg: str):
32
+ super().__init__(msg)