indico_toolkit 6.14.2__tar.gz → 7.2.0__tar.gz
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.
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/PKG-INFO +1 -1
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/__init__.py +1 -1
- indico_toolkit-7.2.0/indico_toolkit/etloutput/__init__.py +157 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/etloutput/cell.py +1 -1
- indico_toolkit-7.2.0/indico_toolkit/etloutput/errors.py +16 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/etloutput/etloutput.py +27 -27
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/etloutput/range.py +1 -1
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/etloutput/table.py +1 -1
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/etloutput/token.py +1 -1
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/polling/autoreview.py +3 -3
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/results/__init__.py +28 -28
- indico_toolkit-7.2.0/indico_toolkit/results/document.py +57 -0
- indico_toolkit-7.2.0/indico_toolkit/results/normalization.py +82 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/results/predictionlist.py +68 -94
- indico_toolkit-7.2.0/indico_toolkit/results/predictions/__init__.py +64 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/results/predictions/box.py +4 -1
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/results/predictions/citation.py +4 -4
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/results/predictions/classification.py +5 -11
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/results/predictions/documentextraction.py +10 -74
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/results/predictions/formextraction.py +15 -20
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/results/predictions/prediction.py +4 -10
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/results/predictions/span.py +1 -1
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/results/predictions/summarization.py +38 -13
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/results/predictions/unbundling.py +7 -7
- indico_toolkit-7.2.0/indico_toolkit/results/result.py +136 -0
- indico_toolkit-7.2.0/indico_toolkit/results/task.py +32 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/results/utils.py +27 -1
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/pyproject.toml +1 -1
- indico_toolkit-6.14.2/indico_toolkit/etloutput/__init__.py +0 -210
- indico_toolkit-6.14.2/indico_toolkit/etloutput/errors.py +0 -16
- indico_toolkit-6.14.2/indico_toolkit/results/document.py +0 -84
- indico_toolkit-6.14.2/indico_toolkit/results/model.py +0 -60
- indico_toolkit-6.14.2/indico_toolkit/results/normalization.py +0 -170
- indico_toolkit-6.14.2/indico_toolkit/results/predictions/__init__.py +0 -88
- indico_toolkit-6.14.2/indico_toolkit/results/result.py +0 -203
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/LICENSE +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/README.md +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/association/__init__.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/association/association.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/association/extracted_tokens.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/association/line_items.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/association/positioning.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/association/splitting.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/auto_review/__init__.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/auto_review/auto_review_functions.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/auto_review/auto_reviewer.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/client.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/errors.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/indico_wrapper/__init__.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/indico_wrapper/download.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/indico_wrapper/indico_wrapper.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/indico_wrapper/modelop.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/indico_wrapper/workflow.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/metrics/__init__.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/metrics/compare_ground_truth.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/metrics/compare_models.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/metrics/metrics.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/metrics/plotting.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/ocr/__init__.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/ocr/ondoc_object.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/ocr/standard_object.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/pipelines/__init__.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/pipelines/file_processing.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/polling/__init__.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/polling/downstream.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/polling/queries.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/py.typed +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/results/errors.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/results/predictions/extraction.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/results/predictions/group.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/results/review.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/retry.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/snapshots/__init__.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/snapshots/snapshot.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/structure/__init__.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/structure/create_structure.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/structure/queries.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/structure/utils.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/types/__init__.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/types/classification.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/types/extractions.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/types/predictions.py +0 -0
- {indico_toolkit-6.14.2 → indico_toolkit-7.2.0}/indico_toolkit/types/workflow_object.py +0 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, TypeAlias, TypeVar
|
|
2
|
+
|
|
3
|
+
from ..results import NULL_BOX, NULL_SPAN, Box, Span
|
|
4
|
+
from ..results.utils import get, has, json_loaded, str_decoded
|
|
5
|
+
from .cell import Cell, CellType
|
|
6
|
+
from .errors import EtlOutputError, TableCellNotFoundError, TokenNotFoundError
|
|
7
|
+
from .etloutput import EtlOutput
|
|
8
|
+
from .range import Range
|
|
9
|
+
from .table import Table
|
|
10
|
+
from .token import Token
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from collections.abc import Awaitable, Callable
|
|
14
|
+
|
|
15
|
+
__all__ = (
|
|
16
|
+
"Box",
|
|
17
|
+
"Cell",
|
|
18
|
+
"CellType",
|
|
19
|
+
"EtlOutput",
|
|
20
|
+
"EtlOutputError",
|
|
21
|
+
"load",
|
|
22
|
+
"load_async",
|
|
23
|
+
"NULL_BOX",
|
|
24
|
+
"NULL_SPAN",
|
|
25
|
+
"Range",
|
|
26
|
+
"Span",
|
|
27
|
+
"Table",
|
|
28
|
+
"TableCellNotFoundError",
|
|
29
|
+
"Token",
|
|
30
|
+
"TokenNotFoundError",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
Loadable: TypeAlias = "dict[str, object] | list[object] | str | bytes"
|
|
34
|
+
Readable = TypeVar("Readable")
|
|
35
|
+
URI: TypeAlias = str
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def load(
|
|
39
|
+
etl_output: "Loadable | Readable",
|
|
40
|
+
*,
|
|
41
|
+
reader: "Callable[[Readable | URI], Loadable]",
|
|
42
|
+
text: bool = True,
|
|
43
|
+
tokens: bool = True,
|
|
44
|
+
tables: bool = True,
|
|
45
|
+
) -> EtlOutput:
|
|
46
|
+
"""
|
|
47
|
+
Load `etl_output` as an `EtlOutput` dataclass.
|
|
48
|
+
|
|
49
|
+
`etl_output` can be a dict, JSON string/bytes, or something that can be read by
|
|
50
|
+
`reader` to produce a loadable type.
|
|
51
|
+
|
|
52
|
+
`reader` must be able to load Indico storage URIs as lists or JSON strings/bytes.
|
|
53
|
+
|
|
54
|
+
Use `text`, `tokens`, and `tables` to specify what not to load.
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
result = results.load(submission.result_file, reader=read_uri)
|
|
58
|
+
etl_outputs = {
|
|
59
|
+
document: etloutput.load(document.etl_output_uri, reader=read_uri)
|
|
60
|
+
for document in result.documents
|
|
61
|
+
if not document.failed
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
"""
|
|
65
|
+
if not isinstance(etl_output, dict):
|
|
66
|
+
if (isinstance(etl_output, str) and etl_output.startswith("{")) or (
|
|
67
|
+
isinstance(etl_output, bytes) and etl_output.startswith(b"{")
|
|
68
|
+
):
|
|
69
|
+
etl_output = json_loaded(etl_output)
|
|
70
|
+
else:
|
|
71
|
+
etl_output = json_loaded(reader(etl_output)) # type: ignore[arg-type]
|
|
72
|
+
|
|
73
|
+
pages = get(etl_output, list, "pages")
|
|
74
|
+
|
|
75
|
+
if text and has(pages, str, 0, "text"):
|
|
76
|
+
text_pages = map(
|
|
77
|
+
lambda page: str_decoded(reader(get(page, str, "text"))), pages # type: ignore[arg-type]
|
|
78
|
+
)
|
|
79
|
+
else:
|
|
80
|
+
text_pages = () # type: ignore[assignment]
|
|
81
|
+
|
|
82
|
+
if tokens and has(pages, str, 0, "tokens"):
|
|
83
|
+
token_dict_pages = map(
|
|
84
|
+
lambda page: json_loaded(reader(get(page, str, "tokens"))), pages
|
|
85
|
+
)
|
|
86
|
+
else:
|
|
87
|
+
token_dict_pages = () # type: ignore[assignment]
|
|
88
|
+
|
|
89
|
+
if tables and has(pages, str, 0, "tables"):
|
|
90
|
+
table_dict_pages = map(
|
|
91
|
+
lambda page: json_loaded(reader(get(page, str, "tables"))), pages
|
|
92
|
+
)
|
|
93
|
+
else:
|
|
94
|
+
table_dict_pages = () # type: ignore[assignment]
|
|
95
|
+
|
|
96
|
+
return EtlOutput.from_pages(text_pages, token_dict_pages, table_dict_pages)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
async def load_async(
|
|
100
|
+
etl_output: "Loadable | Readable",
|
|
101
|
+
*,
|
|
102
|
+
reader: "Callable[[Readable | URI], Awaitable[Loadable]]",
|
|
103
|
+
text: bool = True,
|
|
104
|
+
tokens: bool = True,
|
|
105
|
+
tables: bool = True,
|
|
106
|
+
) -> EtlOutput:
|
|
107
|
+
"""
|
|
108
|
+
Load `etl_output` as an `EtlOutput` dataclass.
|
|
109
|
+
|
|
110
|
+
`etl_output` can be a dict, JSON string/bytes, or something that can be read by
|
|
111
|
+
`reader` to produce a loadable type.
|
|
112
|
+
|
|
113
|
+
`reader` must be able to load Indico storage URIs as lists or JSON strings/bytes.
|
|
114
|
+
|
|
115
|
+
Use `text`, `tokens`, and `tables` to specify what not to load.
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
result = await results.load_async(submission.result_file, reader=read_uri)
|
|
119
|
+
etl_outputs = {
|
|
120
|
+
document: await etloutput.load_async(document.etl_output_uri, reader=read_uri)
|
|
121
|
+
for document in result.documents
|
|
122
|
+
if not document.failed
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
"""
|
|
126
|
+
if not isinstance(etl_output, dict):
|
|
127
|
+
if (isinstance(etl_output, str) and etl_output.startswith("{")) or (
|
|
128
|
+
isinstance(etl_output, bytes) and etl_output.startswith(b"{")
|
|
129
|
+
):
|
|
130
|
+
etl_output = json_loaded(etl_output)
|
|
131
|
+
else:
|
|
132
|
+
etl_output = json_loaded(await reader(etl_output)) # type: ignore[arg-type]
|
|
133
|
+
|
|
134
|
+
pages = get(etl_output, list, "pages")
|
|
135
|
+
|
|
136
|
+
if text and has(pages, str, 0, "text"):
|
|
137
|
+
text_pages = [
|
|
138
|
+
str_decoded(await reader(get(page, str, "text"))) for page in pages # type: ignore[arg-type]
|
|
139
|
+
]
|
|
140
|
+
else:
|
|
141
|
+
text_pages = () # type: ignore[assignment]
|
|
142
|
+
|
|
143
|
+
if tokens and has(pages, str, 0, "tokens"):
|
|
144
|
+
token_dict_pages = [
|
|
145
|
+
json_loaded(await reader(get(page, str, "tokens"))) for page in pages
|
|
146
|
+
]
|
|
147
|
+
else:
|
|
148
|
+
token_dict_pages = () # type: ignore[assignment]
|
|
149
|
+
|
|
150
|
+
if tables and has(pages, str, 0, "tables"):
|
|
151
|
+
table_dict_pages = [
|
|
152
|
+
json_loaded(await reader(get(page, str, "tables"))) for page in pages
|
|
153
|
+
]
|
|
154
|
+
else:
|
|
155
|
+
table_dict_pages = () # type: ignore[assignment]
|
|
156
|
+
|
|
157
|
+
return EtlOutput.from_pages(text_pages, token_dict_pages, table_dict_pages)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class EtlOutputError(Exception):
|
|
2
|
+
"""
|
|
3
|
+
Raised when an error occurs accessing `EtlOutput` values.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TokenNotFoundError(EtlOutputError):
|
|
8
|
+
"""
|
|
9
|
+
Raised when a `Token` can't be found for a `Span`.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TableCellNotFoundError(EtlOutputError):
|
|
14
|
+
"""
|
|
15
|
+
Raised when a `Table` and `Cell` can't be found for a `Token`.
|
|
16
|
+
"""
|
|
@@ -28,30 +28,30 @@ class EtlOutput:
|
|
|
28
28
|
|
|
29
29
|
@staticmethod
|
|
30
30
|
def from_pages(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
text_pages: "Iterable[str]",
|
|
32
|
+
token_dict_pages: "Iterable[Iterable[object]]",
|
|
33
|
+
table_dict_pages: "Iterable[Iterable[object]]",
|
|
34
34
|
) -> "EtlOutput":
|
|
35
35
|
"""
|
|
36
|
-
Create an `EtlOutput` from
|
|
36
|
+
Create an `EtlOutput` from pages of text, tokens, and tables.
|
|
37
37
|
"""
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
text_pages = tuple(text_pages)
|
|
39
|
+
token_pages = tuple(
|
|
40
40
|
tuple(sorted(map(Token.from_dict, token_dict_page), key=attrgetter("span")))
|
|
41
|
-
for token_dict_page in
|
|
41
|
+
for token_dict_page in token_dict_pages
|
|
42
42
|
)
|
|
43
|
-
|
|
43
|
+
table_pages = tuple(
|
|
44
44
|
tuple(sorted(map(Table.from_dict, table_dict_page), key=attrgetter("box")))
|
|
45
|
-
for table_dict_page in
|
|
45
|
+
for table_dict_page in table_dict_pages
|
|
46
46
|
)
|
|
47
47
|
|
|
48
48
|
return EtlOutput(
|
|
49
|
-
text="\n".join(
|
|
50
|
-
text_on_page=
|
|
51
|
-
tokens=tuple(itertools.chain.from_iterable(
|
|
52
|
-
tokens_on_page=
|
|
53
|
-
tables=tuple(itertools.chain.from_iterable(
|
|
54
|
-
tables_on_page=
|
|
49
|
+
text="\n".join(text_pages),
|
|
50
|
+
text_on_page=text_pages,
|
|
51
|
+
tokens=tuple(itertools.chain.from_iterable(token_pages)),
|
|
52
|
+
tokens_on_page=token_pages,
|
|
53
|
+
tables=tuple(itertools.chain.from_iterable(table_pages)),
|
|
54
|
+
tables_on_page=table_pages,
|
|
55
55
|
)
|
|
56
56
|
|
|
57
57
|
def token_for(self, span: Span) -> Token:
|
|
@@ -64,21 +64,21 @@ class EtlOutput:
|
|
|
64
64
|
first = bisect_right(tokens, span.start, key=attrgetter("span.end"))
|
|
65
65
|
last = bisect_left(tokens, span.end, lo=first, key=attrgetter("span.start"))
|
|
66
66
|
tokens = tokens[first:last]
|
|
67
|
-
|
|
68
|
-
return Token(
|
|
69
|
-
text=self.text[span.slice],
|
|
70
|
-
span=span,
|
|
71
|
-
box=Box(
|
|
72
|
-
page=min(token.box.page for token in tokens),
|
|
73
|
-
top=min(token.box.top for token in tokens),
|
|
74
|
-
left=min(token.box.left for token in tokens),
|
|
75
|
-
right=max(token.box.right for token in tokens),
|
|
76
|
-
bottom=max(token.box.bottom for token in tokens),
|
|
77
|
-
),
|
|
78
|
-
)
|
|
79
67
|
except (IndexError, ValueError) as error:
|
|
80
68
|
raise TokenNotFoundError(f"no token contains {span!r}") from error
|
|
81
69
|
|
|
70
|
+
return Token(
|
|
71
|
+
text=self.text[span.slice],
|
|
72
|
+
box=Box(
|
|
73
|
+
page=span.page,
|
|
74
|
+
top=min(token.box.top for token in tokens),
|
|
75
|
+
left=min(token.box.left for token in tokens),
|
|
76
|
+
right=max(token.box.right for token in tokens),
|
|
77
|
+
bottom=max(token.box.bottom for token in tokens),
|
|
78
|
+
),
|
|
79
|
+
span=span,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
82
|
def table_cell_for(self, token: Token) -> "tuple[Table, Cell]":
|
|
83
83
|
"""
|
|
84
84
|
Return the `Table` and `Cell` that contain the midpoint of `token`.
|
|
@@ -16,7 +16,7 @@ class Table:
|
|
|
16
16
|
@staticmethod
|
|
17
17
|
def from_dict(table: object) -> "Table":
|
|
18
18
|
"""
|
|
19
|
-
Create a `Table` from a
|
|
19
|
+
Create a `Table` from a table dictionary.
|
|
20
20
|
"""
|
|
21
21
|
page = get(table, int, "page_num")
|
|
22
22
|
get(table, dict, "position")["page_num"] = page
|
|
@@ -13,7 +13,7 @@ class Token:
|
|
|
13
13
|
@staticmethod
|
|
14
14
|
def from_dict(token: object) -> "Token":
|
|
15
15
|
"""
|
|
16
|
-
Create a `Token` from a
|
|
16
|
+
Create a `Token` from a token dictionary.
|
|
17
17
|
"""
|
|
18
18
|
get(token, dict, "position")["page_num"] = get(token, int, "page_num")
|
|
19
19
|
get(token, dict, "doc_offset")["page_num"] = get(token, int, "page_num")
|
|
@@ -53,7 +53,7 @@ class AutoReviewPoller:
|
|
|
53
53
|
load_etl_output: bool = True,
|
|
54
54
|
load_text: bool = True,
|
|
55
55
|
load_tokens: bool = True,
|
|
56
|
-
load_tables: bool =
|
|
56
|
+
load_tables: bool = True,
|
|
57
57
|
retry_count: int = 4,
|
|
58
58
|
retry_wait: float = 1,
|
|
59
59
|
retry_backoff: float = 4,
|
|
@@ -96,8 +96,8 @@ class AutoReviewPoller:
|
|
|
96
96
|
*(self._reap_workers() for _ in range(self._worker_count)),
|
|
97
97
|
)
|
|
98
98
|
|
|
99
|
-
async def _retrieve_storage_object(self,
|
|
100
|
-
return await self._client_call(RetrieveStorageObject(
|
|
99
|
+
async def _retrieve_storage_object(self, uri: str) -> "Any":
|
|
100
|
+
return await self._client_call(RetrieveStorageObject(uri))
|
|
101
101
|
|
|
102
102
|
async def _spawn_workers(self) -> None:
|
|
103
103
|
"""
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
from typing import TYPE_CHECKING
|
|
1
|
+
from typing import TYPE_CHECKING, TypeAlias, TypeVar, overload
|
|
3
2
|
|
|
4
3
|
from .document import Document
|
|
5
4
|
from .errors import ResultError
|
|
6
|
-
from .model import ModelGroup, ModelGroupType
|
|
7
5
|
from .predictionlist import PredictionList
|
|
8
6
|
from .predictions import (
|
|
9
7
|
NULL_BOX,
|
|
@@ -23,7 +21,8 @@ from .predictions import (
|
|
|
23
21
|
)
|
|
24
22
|
from .result import Result
|
|
25
23
|
from .review import Review, ReviewType
|
|
26
|
-
from .
|
|
24
|
+
from .task import Task, TaskType
|
|
25
|
+
from .utils import json_loaded
|
|
27
26
|
|
|
28
27
|
if TYPE_CHECKING:
|
|
29
28
|
from collections.abc import Awaitable, Callable
|
|
@@ -40,8 +39,6 @@ __all__ = (
|
|
|
40
39
|
"Group",
|
|
41
40
|
"load",
|
|
42
41
|
"load_async",
|
|
43
|
-
"ModelGroup",
|
|
44
|
-
"ModelGroupType",
|
|
45
42
|
"NULL_BOX",
|
|
46
43
|
"NULL_CITATION",
|
|
47
44
|
"NULL_SPAN",
|
|
@@ -53,16 +50,24 @@ __all__ = (
|
|
|
53
50
|
"ReviewType",
|
|
54
51
|
"Span",
|
|
55
52
|
"Summarization",
|
|
53
|
+
"Task",
|
|
54
|
+
"TaskType",
|
|
56
55
|
"Unbundling",
|
|
57
56
|
)
|
|
58
57
|
|
|
58
|
+
Loadable: TypeAlias = "dict[str, object] | str | bytes"
|
|
59
|
+
Readable = TypeVar("Readable")
|
|
59
60
|
|
|
61
|
+
|
|
62
|
+
@overload
|
|
63
|
+
def load(result: Loadable) -> Result: ...
|
|
64
|
+
@overload
|
|
65
|
+
def load(result: Readable, *, reader: "Callable[[Readable], Loadable]") -> Result: ...
|
|
60
66
|
def load(result: object, *, reader: "Callable[..., object] | None" = None) -> Result:
|
|
61
67
|
"""
|
|
62
|
-
Load `result` as a Result dataclass.
|
|
68
|
+
Load `result` as a `Result` dataclass. `result` can be a dict or JSON string/bytes.
|
|
63
69
|
|
|
64
|
-
`
|
|
65
|
-
to produce either.
|
|
70
|
+
If `reader` is provided, it should read `result` to produce a loadable type.
|
|
66
71
|
|
|
67
72
|
```
|
|
68
73
|
for result_file in result_folder.glob("*.json"):
|
|
@@ -72,33 +77,28 @@ def load(result: object, *, reader: "Callable[..., object] | None" = None) -> Re
|
|
|
72
77
|
if reader:
|
|
73
78
|
result = reader(result)
|
|
74
79
|
|
|
75
|
-
return
|
|
80
|
+
return Result.from_dict(json_loaded(result))
|
|
76
81
|
|
|
77
82
|
|
|
83
|
+
@overload
|
|
84
|
+
async def load_async(result: Loadable) -> Result: ...
|
|
85
|
+
@overload
|
|
86
|
+
async def load_async(
|
|
87
|
+
result: Readable, *, reader: "Callable[[Readable], Awaitable[Loadable]]"
|
|
88
|
+
) -> Result: ...
|
|
78
89
|
async def load_async(
|
|
79
90
|
result: object, *, reader: "Callable[..., Awaitable[object]] | None" = None
|
|
80
91
|
) -> Result:
|
|
81
92
|
"""
|
|
82
|
-
Load `result` as a Result dataclass.
|
|
93
|
+
Load `result` as a `Result` dataclass. `result` can be a dict or JSON string/bytes.
|
|
94
|
+
|
|
95
|
+
If `reader` is provided, it should read `result` to produce a loadable type.
|
|
83
96
|
|
|
84
|
-
|
|
85
|
-
|
|
97
|
+
```
|
|
98
|
+
result = await results.load_async(submission.result_file, reader=read_uri)
|
|
99
|
+
```
|
|
86
100
|
"""
|
|
87
101
|
if reader:
|
|
88
102
|
result = await reader(result)
|
|
89
103
|
|
|
90
|
-
return
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def _load(result: object) -> Result:
|
|
94
|
-
if isinstance(result, str) and result.strip().startswith("{"):
|
|
95
|
-
result = json.loads(result)
|
|
96
|
-
|
|
97
|
-
file_version = get(result, int, "file_version")
|
|
98
|
-
|
|
99
|
-
if file_version == 1:
|
|
100
|
-
return Result.from_v1_dict(result)
|
|
101
|
-
elif file_version == 3:
|
|
102
|
-
return Result.from_v3_dict(result)
|
|
103
|
-
else:
|
|
104
|
-
raise ResultError(f"unsupported file version `{file_version}`")
|
|
104
|
+
return Result.from_dict(json_loaded(result))
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from .utils import get
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True, order=True)
|
|
7
|
+
class Document:
|
|
8
|
+
id: int
|
|
9
|
+
name: str
|
|
10
|
+
etl_output_uri: str
|
|
11
|
+
failed: bool
|
|
12
|
+
error: str
|
|
13
|
+
traceback: str
|
|
14
|
+
|
|
15
|
+
# Auto review changes must reproduce all model and component sections that were
|
|
16
|
+
# present in the original result file. This may not be possible from the
|
|
17
|
+
# predictions alone--if a model or component had an empty section because it didn't
|
|
18
|
+
# produce predictions or if all of the predictions for that section were dropped.
|
|
19
|
+
# As such, the model and component IDs seen when parsing a result file are tracked
|
|
20
|
+
# per-document so that the empty sections can be reproduced later.
|
|
21
|
+
_model_ids: "frozenset[str]"
|
|
22
|
+
_component_ids: "frozenset[str]"
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def from_dict(document: object) -> "Document":
|
|
26
|
+
"""
|
|
27
|
+
Create a `Document` from a document dictionary.
|
|
28
|
+
"""
|
|
29
|
+
model_results = get(document, dict, "model_results", "ORIGINAL")
|
|
30
|
+
component_results = get(document, dict, "component_results", "ORIGINAL")
|
|
31
|
+
|
|
32
|
+
return Document(
|
|
33
|
+
id=get(document, int, "submissionfile_id"),
|
|
34
|
+
name=get(document, str, "input_filename"),
|
|
35
|
+
etl_output_uri=get(document, str, "etl_output"),
|
|
36
|
+
failed=False,
|
|
37
|
+
error="",
|
|
38
|
+
traceback="",
|
|
39
|
+
_model_ids=frozenset(model_results.keys()),
|
|
40
|
+
_component_ids=frozenset(component_results.keys()),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def from_errored_file_dict(errored_file: object) -> "Document":
|
|
45
|
+
"""
|
|
46
|
+
Create a `Document` from an errored file dictionary.
|
|
47
|
+
"""
|
|
48
|
+
return Document(
|
|
49
|
+
id=get(errored_file, int, "submissionfile_id"),
|
|
50
|
+
name=get(errored_file, str, "input_filename"),
|
|
51
|
+
etl_output_uri="",
|
|
52
|
+
failed=True,
|
|
53
|
+
error=get(errored_file, str, "error"),
|
|
54
|
+
traceback=get(errored_file, str, "traceback"),
|
|
55
|
+
_model_ids=frozenset(),
|
|
56
|
+
_component_ids=frozenset(),
|
|
57
|
+
)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
from .task import TaskType
|
|
5
|
+
from .utils import get, has
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def normalize_result_dict(result: "Any") -> None:
|
|
12
|
+
"""
|
|
13
|
+
Fix inconsistencies observed in result file structure.
|
|
14
|
+
"""
|
|
15
|
+
for errored_file in get(result, dict, "errored_files").values():
|
|
16
|
+
# Parse filenames for errored files.
|
|
17
|
+
if not has(errored_file, str, "input_filename"):
|
|
18
|
+
reason = get(errored_file, str, "reason")
|
|
19
|
+
match = re.search(r"file '([^']*)' with id", reason)
|
|
20
|
+
errored_file["input_filename"] = match.group(1) if match else ""
|
|
21
|
+
|
|
22
|
+
# Parse error from trackback for errored files.
|
|
23
|
+
if not has(errored_file, str, "traceback"):
|
|
24
|
+
traceback = get(errored_file, str, "error")
|
|
25
|
+
error = traceback.split("\n")[-1].strip()
|
|
26
|
+
errored_file["traceback"] = traceback
|
|
27
|
+
errored_file["error"] = error
|
|
28
|
+
|
|
29
|
+
# Convert `None` review notes to "".
|
|
30
|
+
for review in get(result, dict, "reviews").values():
|
|
31
|
+
if not has(review, str, "review_notes"):
|
|
32
|
+
review["review_notes"] = ""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def normalize_prediction_dict(task_type: TaskType, prediction: "Any") -> None:
|
|
36
|
+
"""
|
|
37
|
+
Fix inconsistencies observed in prediction structure.
|
|
38
|
+
"""
|
|
39
|
+
# Predictions added in review lack a `confidence` section.
|
|
40
|
+
# (And should theoretically have 100% confidence.)
|
|
41
|
+
if not has(prediction, dict, "confidence"):
|
|
42
|
+
prediction["confidence"] = {get(prediction, str, "label"): 1.0}
|
|
43
|
+
|
|
44
|
+
# Extractions added in review may lack a `normalized` section.
|
|
45
|
+
if task_type in (
|
|
46
|
+
TaskType.DOCUMENT_EXTRACTION,
|
|
47
|
+
TaskType.FORM_EXTRACTION,
|
|
48
|
+
TaskType.GENAI_EXTRACTION,
|
|
49
|
+
) and not has(prediction, dict, "normalized"):
|
|
50
|
+
prediction["normalized"] = {"formatted": get(prediction, str, "text")}
|
|
51
|
+
|
|
52
|
+
# Document Extractions added in review may lack a `spans` section.
|
|
53
|
+
# This value will match `NULL_SPAN`.
|
|
54
|
+
if task_type in (
|
|
55
|
+
TaskType.DOCUMENT_EXTRACTION,
|
|
56
|
+
TaskType.GENAI_EXTRACTION,
|
|
57
|
+
) and not has(prediction, list, "spans"):
|
|
58
|
+
prediction["spans"] = []
|
|
59
|
+
|
|
60
|
+
# Form Extractions added in review may lack bounding box information.
|
|
61
|
+
# These values will match `NULL_BOX`.
|
|
62
|
+
if task_type == TaskType.FORM_EXTRACTION and not has(prediction, int, "top"):
|
|
63
|
+
prediction["page_num"] = 0
|
|
64
|
+
prediction["top"] = 0
|
|
65
|
+
prediction["left"] = 0
|
|
66
|
+
prediction["right"] = 0
|
|
67
|
+
prediction["bottom"] = 0
|
|
68
|
+
|
|
69
|
+
# Document Extractions that didn't go through a linked labels transformer
|
|
70
|
+
# lack a `groupings` section.
|
|
71
|
+
if task_type in (
|
|
72
|
+
TaskType.DOCUMENT_EXTRACTION,
|
|
73
|
+
TaskType.GENAI_EXTRACTION,
|
|
74
|
+
) and not has(prediction, list, "groupings"):
|
|
75
|
+
prediction["groupings"] = []
|
|
76
|
+
|
|
77
|
+
# Summarizations added in review may lack a `citations` section.
|
|
78
|
+
# This value will match `NULL_CITATION`.
|
|
79
|
+
if task_type == TaskType.GENAI_SUMMARIZATION and not has(
|
|
80
|
+
prediction, list, "citations"
|
|
81
|
+
):
|
|
82
|
+
prediction["citations"] = []
|