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,88 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from ..model import ModelGroupType
4
+ from .box import NULL_BOX, Box
5
+ from .citation import NULL_CITATION, Citation
6
+ from .classification import Classification
7
+ from .documentextraction import DocumentExtraction
8
+ from .extraction import Extraction
9
+ from .formextraction import FormExtraction, FormExtractionType
10
+ from .group import Group
11
+ from .prediction import Prediction
12
+ from .span import NULL_SPAN, Span
13
+ from .summarization import Summarization
14
+ from .unbundling import Unbundling
15
+
16
+ if TYPE_CHECKING:
17
+ from ..document import Document
18
+ from ..errors import ResultError
19
+ from ..model import ModelGroup
20
+ from ..review import Review
21
+
22
+ __all__ = (
23
+ "Box",
24
+ "Citation",
25
+ "Classification",
26
+ "DocumentExtraction",
27
+ "Extraction",
28
+ "FormExtraction",
29
+ "FormExtractionType",
30
+ "Group",
31
+ "NULL_BOX",
32
+ "NULL_CITATION",
33
+ "NULL_SPAN",
34
+ "Prediction",
35
+ "Span",
36
+ "Summarization",
37
+ "Unbundling",
38
+ )
39
+
40
+ CLASSIFICATION = ModelGroupType.CLASSIFICATION
41
+ DOCUMENT_EXTRACTION = ModelGroupType.DOCUMENT_EXTRACTION
42
+ FORM_EXTRACTION = ModelGroupType.FORM_EXTRACTION
43
+ GENAI_CLASSIFICATION = ModelGroupType.GENAI_CLASSIFICATION
44
+ GENAI_EXTRACTION = ModelGroupType.GENAI_EXTRACTION
45
+ GENAI_SUMMARIZATION = ModelGroupType.GENAI_SUMMARIZATION
46
+ UNBUNDLING = ModelGroupType.UNBUNDLING
47
+
48
+
49
+ def from_v1_dict(
50
+ document: "Document",
51
+ model: "ModelGroup",
52
+ review: "Review | None",
53
+ prediction: object,
54
+ ) -> "Prediction":
55
+ """
56
+ Create a `Prediction` subclass from a v1 prediction dictionary.
57
+ """
58
+ if model.type == CLASSIFICATION:
59
+ return Classification.from_v1_dict(document, model, review, prediction)
60
+ elif model.type == DOCUMENT_EXTRACTION:
61
+ return DocumentExtraction.from_v1_dict(document, model, review, prediction)
62
+ elif model.type == FORM_EXTRACTION:
63
+ return FormExtraction.from_v1_dict(document, model, review, prediction)
64
+ else:
65
+ raise ResultError(f"unsupported v1 model type `{model.type!r}`")
66
+
67
+
68
+ def from_v3_dict(
69
+ document: "Document",
70
+ model: "ModelGroup",
71
+ review: "Review | None",
72
+ prediction: object,
73
+ ) -> "Prediction":
74
+ """
75
+ Create a `Prediction` subclass from a v3 prediction dictionary.
76
+ """
77
+ if model.type in (CLASSIFICATION, GENAI_CLASSIFICATION):
78
+ return Classification.from_v3_dict(document, model, review, prediction)
79
+ elif model.type in (DOCUMENT_EXTRACTION, GENAI_EXTRACTION):
80
+ return DocumentExtraction.from_v3_dict(document, model, review, prediction)
81
+ elif model.type == FORM_EXTRACTION:
82
+ return FormExtraction.from_v3_dict(document, model, review, prediction)
83
+ elif model.type == GENAI_SUMMARIZATION:
84
+ return Summarization.from_v3_dict(document, model, review, prediction)
85
+ elif model.type == UNBUNDLING:
86
+ return Unbundling.from_v3_dict(document, model, review, prediction)
87
+ else:
88
+ raise ResultError(f"unsupported v3 model type `{model.type!r}`")
@@ -0,0 +1,58 @@
1
+ from dataclasses import dataclass
2
+ from typing import TYPE_CHECKING
3
+
4
+ from ..utilities import get
5
+
6
+ if TYPE_CHECKING:
7
+ from typing import Final
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class Box:
12
+ page: int
13
+ top: int
14
+ left: int
15
+ right: int
16
+ bottom: int
17
+
18
+ def __lt__(self, other: "Box") -> bool:
19
+ """
20
+ Bounding boxes are sorted with vertical hysteresis. Those on the same line are
21
+ sorted left-to-right, even when later tokens are higher than earlier ones,
22
+ as long as they overlap vertically.
23
+
24
+ ┌──────────────────┐ ┌───────────────────┐
25
+ │ 1 │ │ 2 │
26
+ └──────────────────┘ │ │
27
+ └───────────────────┘
28
+ ┌────────────────┐
29
+ ┌─────────────┐ │ 4 │ ┌─────┐
30
+ │ 3 │ └────────────────┘ │ 5 │
31
+ └─────────────┘ └─────┘
32
+ """
33
+ return (
34
+ self.page < other.page
35
+ or (self.page == other.page and self.bottom < other.top)
36
+ or (
37
+ self.page == other.page
38
+ and self.top < other.bottom
39
+ and self.left < other.left
40
+ )
41
+ )
42
+
43
+ @staticmethod
44
+ def from_dict(box: object) -> "Box":
45
+ return Box(
46
+ page=get(box, int, "page_num"),
47
+ top=get(box, int, "top"),
48
+ left=get(box, int, "left"),
49
+ right=get(box, int, "right"),
50
+ bottom=get(box, int, "bottom"),
51
+ )
52
+
53
+
54
+ # It's more ergonomic to represent the lack of a bounding box with a special null box
55
+ # object rather than using `None` or raising an error. This lets you e.g. sort by the
56
+ # `box` attribute without having to constantly check for `None`, while still allowing
57
+ # you do a "None check" with `extraction.box == NULL_BOX`.
58
+ NULL_BOX: "Final" = Box(page=0, top=0, left=0, right=0, bottom=0)
@@ -0,0 +1,47 @@
1
+ from dataclasses import dataclass
2
+ from typing import TYPE_CHECKING
3
+
4
+ from ..utilities import get
5
+ from .span import NULL_SPAN, Span
6
+
7
+ if TYPE_CHECKING:
8
+ from typing import Any, Final
9
+
10
+
11
+ @dataclass(order=True, frozen=True)
12
+ class Citation:
13
+ start: int
14
+ end: int
15
+ span: Span
16
+
17
+ @property
18
+ def slice(self) -> slice:
19
+ return slice(self.start, self.end)
20
+
21
+ def __bool__(self) -> bool:
22
+ return self != NULL_CITATION
23
+
24
+ @staticmethod
25
+ def from_dict(span: object) -> "Citation":
26
+ return Citation(
27
+ start=get(span, int, "response", "start"),
28
+ end=get(span, int, "response", "end"),
29
+ span=Span.from_dict(get(span, dict, "document")),
30
+ )
31
+
32
+ def to_dict(self) -> "dict[str, Any]":
33
+ return {
34
+ "document": self.span.to_dict(),
35
+ "response": {
36
+ "start": self.start,
37
+ "end": self.end,
38
+ },
39
+ }
40
+
41
+
42
+ # It's more ergonomic to represent the lack of citations with a special null citation
43
+ # object rather than using `None` or raising an error. This lets you e.g. sort by the
44
+ # `citation` attribute without having to constantly check for `None`, while still
45
+ # allowing you do a "None check" with `summarization.citation == NULL_CITATION` or
46
+ # `bool(summarization.citation)`.
47
+ NULL_CITATION: "Final" = Citation(span=NULL_SPAN, start=0, end=0)
@@ -0,0 +1,50 @@
1
+ from dataclasses import dataclass
2
+ from typing import TYPE_CHECKING
3
+
4
+ from ..review import Review
5
+ from ..utilities import get, omit
6
+ from .prediction import Prediction
7
+
8
+ if TYPE_CHECKING:
9
+ from typing import Any
10
+
11
+ from ..document import Document
12
+ from ..model import ModelGroup
13
+
14
+
15
+ @dataclass
16
+ class Classification(Prediction):
17
+ @staticmethod
18
+ def _from_dict(
19
+ document: "Document",
20
+ model: "ModelGroup",
21
+ review: "Review | None",
22
+ prediction: object,
23
+ ) -> "Classification":
24
+ """
25
+ Create a `Classification` from a prediction dictionary.
26
+ """
27
+ return Classification(
28
+ document=document,
29
+ model=model,
30
+ review=review,
31
+ label=get(prediction, str, "label"),
32
+ confidences=get(prediction, dict, "confidence"),
33
+ extras=omit(prediction, "label", "confidence"),
34
+ )
35
+
36
+ from_v1_dict = _from_dict
37
+ from_v3_dict = _from_dict
38
+
39
+ def _to_dict(self) -> "dict[str, Any]":
40
+ """
41
+ Create a prediction dictionary for auto review changes.
42
+ """
43
+ return {
44
+ **self.extras,
45
+ "label": self.label,
46
+ "confidence": self.confidences,
47
+ }
48
+
49
+ to_v1_dict = _to_dict
50
+ to_v3_dict = _to_dict
@@ -0,0 +1,160 @@
1
+ from dataclasses import dataclass
2
+ from typing import TYPE_CHECKING
3
+
4
+ from ..review import Review
5
+ from ..utilities import get, has, omit
6
+ from .extraction import Extraction
7
+ from .group import Group
8
+ from .span import NULL_SPAN, Span
9
+
10
+ if TYPE_CHECKING:
11
+ from typing import Any
12
+
13
+ from ..document import Document
14
+ from ..model import ModelGroup
15
+
16
+
17
+ @dataclass
18
+ class DocumentExtraction(Extraction):
19
+ groups: "set[Group]"
20
+ spans: "list[Span]"
21
+
22
+ @property
23
+ def span(self) -> Span:
24
+ """
25
+ Return the first `Span` the document extraction covers else `NULL_SPAN`.
26
+
27
+ Post-review, document extractions have no spans.
28
+ """
29
+ return self.spans[0] if self.spans else NULL_SPAN
30
+
31
+ @span.setter
32
+ def span(self, span: Span) -> None:
33
+ """
34
+ Overwrite all `spans` with the one provided.
35
+
36
+ This is implemented under the assumption that if you're setting the single span,
37
+ you want it to be the only one. And if you're working in a context that's
38
+ multiple-span sensetive, you'll set `extraction.spans` instead.
39
+ """
40
+ self.spans = [span]
41
+
42
+ @staticmethod
43
+ def from_v1_dict(
44
+ document: "Document",
45
+ model: "ModelGroup",
46
+ review: "Review | None",
47
+ prediction: object,
48
+ ) -> "DocumentExtraction":
49
+ """
50
+ Create a `DocumentExtraction` from a v1 prediction dictionary.
51
+ """
52
+ return DocumentExtraction(
53
+ document=document,
54
+ model=model,
55
+ review=review,
56
+ label=get(prediction, str, "label"),
57
+ confidences=get(prediction, dict, "confidence"),
58
+ text=get(prediction, str, "normalized", "formatted"),
59
+ accepted=(
60
+ has(prediction, bool, "accepted") and get(prediction, bool, "accepted")
61
+ ),
62
+ rejected=(
63
+ has(prediction, bool, "rejected") and get(prediction, bool, "rejected")
64
+ ),
65
+ groups=set(map(Group.from_dict, get(prediction, list, "groupings"))),
66
+ spans=[Span.from_dict(prediction)] if has(prediction, int, "start") else [],
67
+ extras=omit(
68
+ prediction,
69
+ "label",
70
+ "confidence",
71
+ "accepted",
72
+ "rejected",
73
+ "groupings",
74
+ "page_num",
75
+ "start",
76
+ "end",
77
+ ),
78
+ )
79
+
80
+ @staticmethod
81
+ def from_v3_dict(
82
+ document: "Document",
83
+ model: "ModelGroup",
84
+ review: "Review | None",
85
+ prediction: object,
86
+ ) -> "DocumentExtraction":
87
+ """
88
+ Create a `DocumentExtraction` from a v3 prediction dictionary.
89
+ """
90
+ return DocumentExtraction(
91
+ document=document,
92
+ model=model,
93
+ review=review,
94
+ label=get(prediction, str, "label"),
95
+ confidences=get(prediction, dict, "confidence"),
96
+ text=get(prediction, str, "normalized", "formatted"),
97
+ accepted=(
98
+ has(prediction, bool, "accepted") and get(prediction, bool, "accepted")
99
+ ),
100
+ rejected=(
101
+ has(prediction, bool, "rejected") and get(prediction, bool, "rejected")
102
+ ),
103
+ groups=set(map(Group.from_dict, get(prediction, list, "groupings"))),
104
+ spans=sorted(map(Span.from_dict, get(prediction, list, "spans"))),
105
+ extras=omit(
106
+ prediction,
107
+ "label",
108
+ "confidence",
109
+ "accepted",
110
+ "rejected",
111
+ "groupings",
112
+ "spans",
113
+ ),
114
+ )
115
+
116
+ def to_v1_dict(self) -> "dict[str, Any]":
117
+ """
118
+ Create a prediction dictionary for v1 auto review changes.
119
+ """
120
+ prediction = {
121
+ **self.extras,
122
+ "label": self.label,
123
+ "confidence": self.confidences,
124
+ "groupings": [group.to_dict() for group in self.groups],
125
+ "page_num": self.span.page,
126
+ "start": self.span.start,
127
+ "end": self.span.end,
128
+ }
129
+
130
+ prediction["normalized"]["formatted"] = self.text
131
+ prediction["text"] = self.text # 6.10 sometimes reverts to raw text in review.
132
+
133
+ if self.accepted:
134
+ prediction["accepted"] = True
135
+ elif self.rejected:
136
+ prediction["rejected"] = True
137
+
138
+ return prediction
139
+
140
+ def to_v3_dict(self) -> "dict[str, Any]":
141
+ """
142
+ Create a prediction dictionary for v3 auto review changes.
143
+ """
144
+ prediction = {
145
+ **self.extras,
146
+ "label": self.label,
147
+ "confidence": self.confidences,
148
+ "groupings": [group.to_dict() for group in self.groups],
149
+ "spans": [span.to_dict() for span in self.spans],
150
+ }
151
+
152
+ prediction["normalized"]["formatted"] = self.text
153
+ prediction["text"] = self.text # 6.10 sometimes reverts to raw text in review.
154
+
155
+ if self.accepted:
156
+ prediction["accepted"] = True
157
+ elif self.rejected:
158
+ prediction["rejected"] = True
159
+
160
+ return prediction
@@ -0,0 +1,35 @@
1
+ from dataclasses import dataclass
2
+
3
+ from .prediction import Prediction
4
+
5
+
6
+ @dataclass
7
+ class Extraction(Prediction):
8
+ text: str
9
+ accepted: bool
10
+ rejected: bool
11
+
12
+ @property
13
+ def page(self) -> int:
14
+ """
15
+ Convenience property to get an extraction's page without knowing its subclass.
16
+ Allows you to do `predictions.extractions.groupby(attrgetter("page"))` et al.
17
+ """
18
+ if hasattr(self, "box"):
19
+ return self.box.page # type: ignore
20
+ else:
21
+ return self.span.page # type: ignore
22
+
23
+ def accept(self) -> None:
24
+ self.accepted = True
25
+ self.rejected = False
26
+
27
+ def unaccept(self) -> None:
28
+ self.accepted = False
29
+
30
+ def reject(self) -> None:
31
+ self.accepted = False
32
+ self.rejected = True
33
+
34
+ def unreject(self) -> None:
35
+ self.rejected = False
@@ -0,0 +1,121 @@
1
+ from dataclasses import dataclass
2
+ from enum import Enum
3
+ from typing import TYPE_CHECKING
4
+
5
+ from ..review import Review
6
+ from ..utilities import get, has, omit
7
+ from .box import Box
8
+ from .extraction import Extraction
9
+
10
+ if TYPE_CHECKING:
11
+ from typing import Any
12
+
13
+ from ..document import Document
14
+ from ..model import ModelGroup
15
+
16
+
17
+ class FormExtractionType(Enum):
18
+ CHECKBOX = "checkbox"
19
+ SIGNATURE = "signature"
20
+ TEXT = "text"
21
+
22
+
23
+ @dataclass
24
+ class FormExtraction(Extraction):
25
+ type: FormExtractionType
26
+ box: Box
27
+ checked: bool
28
+ signed: bool
29
+
30
+ @staticmethod
31
+ def _from_dict(
32
+ document: "Document",
33
+ model: "ModelGroup",
34
+ review: "Review | None",
35
+ prediction: object,
36
+ ) -> "FormExtraction":
37
+ """
38
+ Create a `FormExtraction` from a prediction dictionary.
39
+ """
40
+ return FormExtraction(
41
+ document=document,
42
+ model=model,
43
+ review=review,
44
+ label=get(prediction, str, "label"),
45
+ confidences=get(prediction, dict, "confidence"),
46
+ text=get(prediction, str, "normalized", "formatted"),
47
+ accepted=(
48
+ has(prediction, bool, "accepted") and get(prediction, bool, "accepted")
49
+ ),
50
+ rejected=(
51
+ has(prediction, bool, "rejected") and get(prediction, bool, "rejected")
52
+ ),
53
+ type=FormExtractionType(get(prediction, str, "type")),
54
+ box=Box.from_dict(prediction),
55
+ checked=(
56
+ has(prediction, bool, "normalized", "structured", "checked")
57
+ and get(prediction, bool, "normalized", "structured", "checked")
58
+ ),
59
+ signed=(
60
+ has(prediction, bool, "normalized", "structured", "signed")
61
+ and get(prediction, bool, "normalized", "structured", "signed")
62
+ ),
63
+ extras=omit(
64
+ prediction,
65
+ "label",
66
+ "confidence",
67
+ "accepted",
68
+ "rejected",
69
+ "type",
70
+ "page_num",
71
+ "top",
72
+ "left",
73
+ "right",
74
+ "bottom",
75
+ "checked",
76
+ "signed",
77
+ ),
78
+ )
79
+
80
+ from_v1_dict = _from_dict
81
+ from_v3_dict = _from_dict
82
+
83
+ def _to_dict(self) -> "dict[str, Any]":
84
+ """
85
+ Create a prediction dictionary for auto review changes.
86
+ """
87
+ prediction = {
88
+ **self.extras,
89
+ "label": self.label,
90
+ "confidence": self.confidences,
91
+ "type": self.type.value,
92
+ "page_num": self.box.page,
93
+ "top": self.box.top,
94
+ "left": self.box.left,
95
+ "right": self.box.right,
96
+ "bottom": self.box.bottom,
97
+ }
98
+
99
+ if self.type == FormExtractionType.CHECKBOX:
100
+ prediction["normalized"]["structured"]["checked"] = self.checked
101
+ prediction["normalized"]["formatted"] = (
102
+ "Checked" if self.checked else "Unchecked"
103
+ )
104
+ elif self.type == FormExtractionType.SIGNATURE:
105
+ prediction["normalized"]["structured"]["signed"] = self.signed
106
+ prediction["normalized"]["formatted"] = (
107
+ "Signed" if self.signed else "Unsigned"
108
+ )
109
+ elif self.type == FormExtractionType.TEXT:
110
+ prediction["normalized"]["formatted"] = self.text
111
+ prediction["text"] = self.text # 6.10 sometimes reverts to text in review.
112
+
113
+ if self.accepted:
114
+ prediction["accepted"] = True
115
+ elif self.rejected:
116
+ prediction["rejected"] = True
117
+
118
+ return prediction
119
+
120
+ to_v1_dict = _to_dict
121
+ to_v3_dict = _to_dict
@@ -0,0 +1,29 @@
1
+ from dataclasses import dataclass
2
+ from typing import TYPE_CHECKING
3
+
4
+ from ..utilities import get
5
+
6
+ if TYPE_CHECKING:
7
+ from typing import Any
8
+
9
+
10
+ @dataclass(frozen=True, order=True)
11
+ class Group:
12
+ id: int
13
+ name: str
14
+ index: int
15
+
16
+ @staticmethod
17
+ def from_dict(group: object) -> "Group":
18
+ return Group(
19
+ id=int(get(group, str, "group_id").split(":")[0]),
20
+ name=get(group, str, "group_name"),
21
+ index=get(group, int, "group_index"),
22
+ )
23
+
24
+ def to_dict(self) -> "dict[str, Any]":
25
+ return {
26
+ "group_id": f"{self.id}:{self.name}",
27
+ "group_name": self.name,
28
+ "group_index": self.index,
29
+ }
@@ -0,0 +1,41 @@
1
+ from dataclasses import dataclass
2
+ from typing import TYPE_CHECKING
3
+
4
+ from ..review import Review
5
+
6
+ if TYPE_CHECKING:
7
+ from typing import Any
8
+
9
+ from ..document import Document
10
+ from ..model import ModelGroup
11
+
12
+
13
+ @dataclass
14
+ class Prediction:
15
+ document: "Document"
16
+ model: "ModelGroup"
17
+ review: "Review | None"
18
+
19
+ label: str
20
+ confidences: "dict[str, float]"
21
+ extras: "dict[str, Any]"
22
+
23
+ @property
24
+ def confidence(self) -> float:
25
+ return self.confidences[self.label]
26
+
27
+ @confidence.setter
28
+ def confidence(self, value: float) -> None:
29
+ self.confidences[self.label] = value
30
+
31
+ def to_v1_dict(self) -> "dict[str, Any]":
32
+ """
33
+ Create a prediction dictionary for v1 auto review changes.
34
+ """
35
+ raise NotImplementedError()
36
+
37
+ def to_v3_dict(self) -> "dict[str, Any]":
38
+ """
39
+ Create a prediction dictionary for v3 auto review changes.
40
+ """
41
+ raise NotImplementedError()
@@ -0,0 +1,43 @@
1
+ from dataclasses import dataclass
2
+ from typing import TYPE_CHECKING
3
+
4
+ from ..utilities import get
5
+
6
+ if TYPE_CHECKING:
7
+ from typing import Any, Final
8
+
9
+
10
+ @dataclass(order=True, frozen=True)
11
+ class Span:
12
+ page: int
13
+ start: int
14
+ end: int
15
+
16
+ @property
17
+ def slice(self) -> slice:
18
+ return slice(self.start, self.end)
19
+
20
+ def __bool__(self) -> bool:
21
+ return self != NULL_SPAN
22
+
23
+ @staticmethod
24
+ def from_dict(span: object) -> "Span":
25
+ return Span(
26
+ page=get(span, int, "page_num"),
27
+ start=get(span, int, "start"),
28
+ end=get(span, int, "end"),
29
+ )
30
+
31
+ def to_dict(self) -> "dict[str, Any]":
32
+ return {
33
+ "page_num": self.page,
34
+ "start": self.start,
35
+ "end": self.end,
36
+ }
37
+
38
+
39
+ # It's more ergonomic to represent the lack of spans with a special null span object
40
+ # rather than using `None` or raising an error. This lets you e.g. sort by the `span`
41
+ # attribute without having to constantly check for `None`, while still allowing you do
42
+ # a "None check" with `extraction.span == NULL_SPAN` or `bool(extraction.span)`.
43
+ NULL_SPAN: "Final" = Span(page=0, start=0, end=0)