parse-bench 1.0.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.
- parse_bench/__init__.py +3 -0
- parse_bench/analysis/__init__.py +6 -0
- parse_bench/analysis/aggregation_report.py +582 -0
- parse_bench/analysis/cli.py +472 -0
- parse_bench/analysis/comparison.py +382 -0
- parse_bench/analysis/comparison_core.py +357 -0
- parse_bench/analysis/comparison_report.py +2066 -0
- parse_bench/analysis/detailed_report.py +2254 -0
- parse_bench/analysis/leaderboard_report.py +852 -0
- parse_bench/analysis/metric_definitions.py +771 -0
- parse_bench/cli.py +267 -0
- parse_bench/data/__init__.py +1 -0
- parse_bench/data/cli.py +118 -0
- parse_bench/data/download.py +127 -0
- parse_bench/evaluation/__init__.py +11 -0
- parse_bench/evaluation/cli.py +435 -0
- parse_bench/evaluation/evaluators/__init__.py +17 -0
- parse_bench/evaluation/evaluators/base.py +34 -0
- parse_bench/evaluation/evaluators/extract.py +429 -0
- parse_bench/evaluation/evaluators/layoutdet.py +1682 -0
- parse_bench/evaluation/evaluators/parse.py +1353 -0
- parse_bench/evaluation/evaluators/qa.py +199 -0
- parse_bench/evaluation/layout_adapters/__init__.py +21 -0
- parse_bench/evaluation/layout_adapters/adapters.py +3180 -0
- parse_bench/evaluation/layout_adapters/base.py +105 -0
- parse_bench/evaluation/layout_adapters/registry.py +109 -0
- parse_bench/evaluation/layout_label_mappers/__init__.py +22 -0
- parse_bench/evaluation/layout_label_mappers/base.py +66 -0
- parse_bench/evaluation/layout_label_mappers/mappers.py +332 -0
- parse_bench/evaluation/layout_label_mappers/projection.py +74 -0
- parse_bench/evaluation/layout_label_mappers/registry.py +119 -0
- parse_bench/evaluation/metric_aggregation.py +56 -0
- parse_bench/evaluation/metrics/__init__.py +5 -0
- parse_bench/evaluation/metrics/attribution/__init__.py +35 -0
- parse_bench/evaluation/metrics/attribution/constants.py +12 -0
- parse_bench/evaluation/metrics/attribution/core.py +1108 -0
- parse_bench/evaluation/metrics/attribution/evaluate.py +446 -0
- parse_bench/evaluation/metrics/attribution/geometry.py +161 -0
- parse_bench/evaluation/metrics/attribution/text_utils.py +233 -0
- parse_bench/evaluation/metrics/base.py +33 -0
- parse_bench/evaluation/metrics/downstream/__init__.py +0 -0
- parse_bench/evaluation/metrics/extract/__init__.py +29 -0
- parse_bench/evaluation/metrics/extract/json_subset_match.py +473 -0
- parse_bench/evaluation/metrics/extract/json_subset_match_metric.py +81 -0
- parse_bench/evaluation/metrics/extract/list_unwrap.py +340 -0
- parse_bench/evaluation/metrics/extract/rule_based_metric.py +90 -0
- parse_bench/evaluation/metrics/extract/test_rules.py +409 -0
- parse_bench/evaluation/metrics/extract/test_types.py +11 -0
- parse_bench/evaluation/metrics/field_grounding/__init__.py +21 -0
- parse_bench/evaluation/metrics/field_grounding/core.py +437 -0
- parse_bench/evaluation/metrics/field_grounding/extract_adapter.py +1224 -0
- parse_bench/evaluation/metrics/field_grounding/parse_adapter.py +697 -0
- parse_bench/evaluation/metrics/field_grounding/rule_filters.py +19 -0
- parse_bench/evaluation/metrics/field_grounding/value_compare.py +190 -0
- parse_bench/evaluation/metrics/layoutdet/__init__.py +17 -0
- parse_bench/evaluation/metrics/layoutdet/classification_utils.py +300 -0
- parse_bench/evaluation/metrics/layoutdet/iou.py +76 -0
- parse_bench/evaluation/metrics/parse/__init__.py +5 -0
- parse_bench/evaluation/metrics/parse/_vendor_grits_reference.py +531 -0
- parse_bench/evaluation/metrics/parse/cross_page_table_consistency.py +165 -0
- parse_bench/evaluation/metrics/parse/emphasis_spans.py +242 -0
- parse_bench/evaluation/metrics/parse/fast_tree_edit.py +282 -0
- parse_bench/evaluation/metrics/parse/grits_metric.py +1125 -0
- parse_bench/evaluation/metrics/parse/grits_reference_metric.py +142 -0
- parse_bench/evaluation/metrics/parse/header_accuracy_metric.py +1662 -0
- parse_bench/evaluation/metrics/parse/llm_normalization/__init__.py +51 -0
- parse_bench/evaluation/metrics/parse/llm_normalization/base.py +125 -0
- parse_bench/evaluation/metrics/parse/llm_normalization/config.py +44 -0
- parse_bench/evaluation/metrics/parse/llm_normalization/postprocess.py +322 -0
- parse_bench/evaluation/metrics/parse/llm_normalization/strategy_judge.py +541 -0
- parse_bench/evaluation/metrics/parse/mermaid_graph.py +682 -0
- parse_bench/evaluation/metrics/parse/rule_based_judge_metric.py +56 -0
- parse_bench/evaluation/metrics/parse/rule_based_metric.py +434 -0
- parse_bench/evaluation/metrics/parse/rules_bag.py +1161 -0
- parse_bench/evaluation/metrics/parse/rules_base.py +751 -0
- parse_bench/evaluation/metrics/parse/rules_chart.py +1556 -0
- parse_bench/evaluation/metrics/parse/rules_diagram.py +591 -0
- parse_bench/evaluation/metrics/parse/rules_form.py +2274 -0
- parse_bench/evaluation/metrics/parse/rules_formatting.py +1500 -0
- parse_bench/evaluation/metrics/parse/rules_heading.py +228 -0
- parse_bench/evaluation/metrics/parse/rules_list.py +226 -0
- parse_bench/evaluation/metrics/parse/rules_page_decoration.py +276 -0
- parse_bench/evaluation/metrics/parse/rules_table.py +1666 -0
- parse_bench/evaluation/metrics/parse/rules_text.py +340 -0
- parse_bench/evaluation/metrics/parse/rules_watermark.py +105 -0
- parse_bench/evaluation/metrics/parse/structural_consistency_metric.py +251 -0
- parse_bench/evaluation/metrics/parse/table_extraction.py +152 -0
- parse_bench/evaluation/metrics/parse/table_merging.py +195 -0
- parse_bench/evaluation/metrics/parse/table_pairing.py +87 -0
- parse_bench/evaluation/metrics/parse/table_parsing.py +955 -0
- parse_bench/evaluation/metrics/parse/table_record_match_metric.py +1453 -0
- parse_bench/evaluation/metrics/parse/table_splitting.py +301 -0
- parse_bench/evaluation/metrics/parse/table_title_stripping.py +530 -0
- parse_bench/evaluation/metrics/parse/teds_metric.py +600 -0
- parse_bench/evaluation/metrics/parse/test_rules.py +120 -0
- parse_bench/evaluation/metrics/parse/test_types.py +103 -0
- parse_bench/evaluation/metrics/parse/text_content_projection.py +175 -0
- parse_bench/evaluation/metrics/parse/text_similarity_metric.py +61 -0
- parse_bench/evaluation/metrics/parse/utils.py +885 -0
- parse_bench/evaluation/metrics/qa/__init__.py +5 -0
- parse_bench/evaluation/metrics/qa/answer_comparison.py +380 -0
- parse_bench/evaluation/qa/__init__.py +5 -0
- parse_bench/evaluation/qa/llm_service.py +335 -0
- parse_bench/evaluation/reports/__init__.py +8 -0
- parse_bench/evaluation/reports/csv.py +64 -0
- parse_bench/evaluation/reports/html.py +338 -0
- parse_bench/evaluation/reports/markdown.py +98 -0
- parse_bench/evaluation/reports/rule_csv.py +22 -0
- parse_bench/evaluation/runner.py +1864 -0
- parse_bench/evaluation/stats.py +104 -0
- parse_bench/extensions.py +72 -0
- parse_bench/inference/__init__.py +33 -0
- parse_bench/inference/chunkr_layout_extraction.py +160 -0
- parse_bench/inference/cli.py +484 -0
- parse_bench/inference/layout_extraction.py +422 -0
- parse_bench/inference/pipelines/__init__.py +59 -0
- parse_bench/inference/pipelines/extract.py +39 -0
- parse_bench/inference/pipelines/layout.py +142 -0
- parse_bench/inference/pipelines/parse.py +2603 -0
- parse_bench/inference/pipelines.py +0 -0
- parse_bench/inference/providers/__init__.py +28 -0
- parse_bench/inference/providers/base.py +196 -0
- parse_bench/inference/providers/cancellation.py +137 -0
- parse_bench/inference/providers/extract/__init__.py +22 -0
- parse_bench/inference/providers/extract/citations.py +549 -0
- parse_bench/inference/providers/extract/extend.py +851 -0
- parse_bench/inference/providers/extract/llamaextract_v2_api.py +583 -0
- parse_bench/inference/providers/layoutdet/__init__.py +25 -0
- parse_bench/inference/providers/layoutdet/adapters.py +946 -0
- parse_bench/inference/providers/layoutdet/base.py +203 -0
- parse_bench/inference/providers/layoutdet/chandra.py +449 -0
- parse_bench/inference/providers/layoutdet/docling.py +125 -0
- parse_bench/inference/providers/layoutdet/dots_ocr.py +606 -0
- parse_bench/inference/providers/layoutdet/layout_v3.py +137 -0
- parse_bench/inference/providers/layoutdet/layout_v3_byoc.py +204 -0
- parse_bench/inference/providers/layoutdet/paddle.py +117 -0
- parse_bench/inference/providers/layoutdet/qwen3vl.py +360 -0
- parse_bench/inference/providers/layoutdet/surya.py +250 -0
- parse_bench/inference/providers/layoutdet/yolo.py +109 -0
- parse_bench/inference/providers/parse/__init__.py +64 -0
- parse_bench/inference/providers/parse/_docling_common.py +233 -0
- parse_bench/inference/providers/parse/_layout_utils.py +611 -0
- parse_bench/inference/providers/parse/amazon_nova.py +515 -0
- parse_bench/inference/providers/parse/anthropic.py +882 -0
- parse_bench/inference/providers/parse/azure_document_intelligence.py +700 -0
- parse_bench/inference/providers/parse/chandra2.py +633 -0
- parse_bench/inference/providers/parse/chunkr.py +268 -0
- parse_bench/inference/providers/parse/databricks_ai_parse.py +724 -0
- parse_bench/inference/providers/parse/datalab.py +370 -0
- parse_bench/inference/providers/parse/deepseekocr2.py +382 -0
- parse_bench/inference/providers/parse/docling.py +281 -0
- parse_bench/inference/providers/parse/docling_serve.py +289 -0
- parse_bench/inference/providers/parse/dots_ocr.py +574 -0
- parse_bench/inference/providers/parse/extend_parse.py +710 -0
- parse_bench/inference/providers/parse/falconocr.py +436 -0
- parse_bench/inference/providers/parse/florin_parser_nano.py +559 -0
- parse_bench/inference/providers/parse/gemma4.py +472 -0
- parse_bench/inference/providers/parse/glm_zai.py +229 -0
- parse_bench/inference/providers/parse/google.py +1125 -0
- parse_bench/inference/providers/parse/google_agentic_vision.py +819 -0
- parse_bench/inference/providers/parse/google_docai.py +776 -0
- parse_bench/inference/providers/parse/google_docai_layout_normalization.py +573 -0
- parse_bench/inference/providers/parse/granite_vision.py +515 -0
- parse_bench/inference/providers/parse/infinity_parser2.py +704 -0
- parse_bench/inference/providers/parse/kdl_frontier_nano.py +3327 -0
- parse_bench/inference/providers/parse/landingai.py +452 -0
- parse_bench/inference/providers/parse/liteparse.py +350 -0
- parse_bench/inference/providers/parse/llamaparse.py +677 -0
- parse_bench/inference/providers/parse/llamaparse_v2_normalization.py +1013 -0
- parse_bench/inference/providers/parse/markitdown.py +138 -0
- parse_bench/inference/providers/parse/mineru25.py +405 -0
- parse_bench/inference/providers/parse/mineru2605pro.py +432 -0
- parse_bench/inference/providers/parse/mineru_diffusion.py +371 -0
- parse_bench/inference/providers/parse/mistral_ocr.py +546 -0
- parse_bench/inference/providers/parse/nemotron_omni.py +473 -0
- parse_bench/inference/providers/parse/oi_parser.py +222 -0
- parse_bench/inference/providers/parse/openai.py +740 -0
- parse_bench/inference/providers/parse/opendataloader.py +152 -0
- parse_bench/inference/providers/parse/paddleocr.py +624 -0
- parse_bench/inference/providers/parse/pdf_inspector.py +142 -0
- parse_bench/inference/providers/parse/pulse.py +785 -0
- parse_bench/inference/providers/parse/pymupdf.py +207 -0
- parse_bench/inference/providers/parse/pymupdf4llm.py +356 -0
- parse_bench/inference/providers/parse/pypdf.py +179 -0
- parse_bench/inference/providers/parse/qwen.py +678 -0
- parse_bench/inference/providers/parse/rakedoc_nano.py +70 -0
- parse_bench/inference/providers/parse/reducto.py +546 -0
- parse_bench/inference/providers/parse/surya2.py +372 -0
- parse_bench/inference/providers/parse/tesseract.py +301 -0
- parse_bench/inference/providers/parse/textract.py +694 -0
- parse_bench/inference/providers/parse/unlimitedocr.py +346 -0
- parse_bench/inference/providers/parse/unstructured.py +485 -0
- parse_bench/inference/providers/parse/warp_ingest.py +199 -0
- parse_bench/inference/providers/registry.py +49 -0
- parse_bench/inference/renormalize.py +170 -0
- parse_bench/inference/runner.py +2023 -0
- parse_bench/layout_label_mapping.py +424 -0
- parse_bench/layout_projection.py +179 -0
- parse_bench/pipeline/__init__.py +1 -0
- parse_bench/pipeline/cli.py +549 -0
- parse_bench/schemas/__init__.py +33 -0
- parse_bench/schemas/evaluation.py +93 -0
- parse_bench/schemas/extract_output.py +36 -0
- parse_bench/schemas/layout_detection_output.py +545 -0
- parse_bench/schemas/layout_ontology.py +315 -0
- parse_bench/schemas/metrics.py +69 -0
- parse_bench/schemas/parse_output.py +152 -0
- parse_bench/schemas/pipeline.py +22 -0
- parse_bench/schemas/pipeline_io.py +106 -0
- parse_bench/schemas/product.py +97 -0
- parse_bench/test_cases/__init__.py +25 -0
- parse_bench/test_cases/bbox_value_strict_comparator.py +880 -0
- parse_bench/test_cases/extract_field_paths.py +164 -0
- parse_bench/test_cases/layout_attribution_generation.py +287 -0
- parse_bench/test_cases/loader.py +652 -0
- parse_bench/test_cases/parse_rule_schemas.py +1071 -0
- parse_bench/test_cases/rule_filters.py +32 -0
- parse_bench/test_cases/rule_ids.py +107 -0
- parse_bench/test_cases/schema.py +427 -0
- parse_bench/utils/__init__.py +15 -0
- parse_bench/utils/gemini_layout_utils.py +670 -0
- parse_bench/utils/text_aggregation.py +100 -0
- parse_bench-1.0.0.dist-info/METADATA +476 -0
- parse_bench-1.0.0.dist-info/RECORD +227 -0
- parse_bench-1.0.0.dist-info/WHEEL +4 -0
- parse_bench-1.0.0.dist-info/entry_points.txt +2 -0
- parse_bench-1.0.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Test rule implementations for parse evaluation.
|
|
2
|
+
|
|
3
|
+
This module re-exports all rule classes and helpers from the split submodules
|
|
4
|
+
for backward compatibility. New code should import directly from the
|
|
5
|
+
specific submodule (rules_base, rules_text, rules_bag, rules_formatting,
|
|
6
|
+
rules_table, rules_chart, rules_form).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
# Base class, helpers, and factory
|
|
10
|
+
# Bag rules
|
|
11
|
+
from parse_bench.evaluation.metrics.parse.rules_bag import ( # noqa: F401
|
|
12
|
+
BagOfDigitPercentRule,
|
|
13
|
+
ExtraContentRule,
|
|
14
|
+
MissingSentencePercentRule,
|
|
15
|
+
MissingSentenceRule,
|
|
16
|
+
MissingSpecificSentenceRule,
|
|
17
|
+
MissingSpecificWordRule,
|
|
18
|
+
MissingWordPercentRule,
|
|
19
|
+
MissingWordRule,
|
|
20
|
+
SentenceBagRule,
|
|
21
|
+
TooManySentenceOccurencePercentRule,
|
|
22
|
+
TooManySentenceOccurenceRule,
|
|
23
|
+
TooManyWordOccurencePercentRule,
|
|
24
|
+
TooManyWordOccurenceRule,
|
|
25
|
+
UnexpectedSentencePercentRule,
|
|
26
|
+
UnexpectedSentenceRule,
|
|
27
|
+
UnexpectedWordPercentRule,
|
|
28
|
+
UnexpectedWordRule,
|
|
29
|
+
WordBagRule,
|
|
30
|
+
)
|
|
31
|
+
from parse_bench.evaluation.metrics.parse.rules_base import ( # noqa: F401
|
|
32
|
+
CELL_FUZZY_MATCH_THRESHOLD,
|
|
33
|
+
AdjacentTableRuleData,
|
|
34
|
+
NoBorderTableRuleData,
|
|
35
|
+
ParseTestRule,
|
|
36
|
+
SentenceBagRuleData,
|
|
37
|
+
WordBagRuleData,
|
|
38
|
+
_augment_with_table_cell_text,
|
|
39
|
+
_dates_match,
|
|
40
|
+
_detect_csv_skip_rows,
|
|
41
|
+
_detect_header_scale,
|
|
42
|
+
_extract_table_cell_texts,
|
|
43
|
+
_normalize_date_str,
|
|
44
|
+
_strip_and_replace_latex,
|
|
45
|
+
_strip_fenced_code_blocks,
|
|
46
|
+
_strip_html_tables_and_content,
|
|
47
|
+
_unescape_html_entities,
|
|
48
|
+
create_test_rule,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Chart rules
|
|
52
|
+
from parse_bench.evaluation.metrics.parse.rules_chart import ( # noqa: F401
|
|
53
|
+
ChartDataArrayDataRule,
|
|
54
|
+
ChartDataArrayLabelsRule,
|
|
55
|
+
ChartDataPointRule,
|
|
56
|
+
RotateCheckRule,
|
|
57
|
+
extract_numeric_parts,
|
|
58
|
+
normalize_number_string,
|
|
59
|
+
numbers_match,
|
|
60
|
+
numeric_similarity,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Form rules
|
|
64
|
+
from parse_bench.evaluation.metrics.parse.rules_form import ( # noqa: F401
|
|
65
|
+
FormFieldRule,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Formatting rules
|
|
69
|
+
from parse_bench.evaluation.metrics.parse.rules_formatting import ( # noqa: F401
|
|
70
|
+
_FORMATTING_TEST_TYPES,
|
|
71
|
+
AbsentUnlessStrikeoutRule,
|
|
72
|
+
CodeBlockRule,
|
|
73
|
+
FormattingRule,
|
|
74
|
+
LatexRule,
|
|
75
|
+
MarkColorRule,
|
|
76
|
+
NotLatexRule,
|
|
77
|
+
PageSectionRule,
|
|
78
|
+
PresentAsStrikeoutRule,
|
|
79
|
+
TextColorRule,
|
|
80
|
+
TitleHierarchyPercentRule,
|
|
81
|
+
TitleLevelRule,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# List rules
|
|
85
|
+
from parse_bench.evaluation.metrics.parse.rules_list import ( # noqa: F401
|
|
86
|
+
ListLevelRule,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Table rules
|
|
90
|
+
from parse_bench.evaluation.metrics.parse.rules_table import ( # noqa: F401
|
|
91
|
+
TableAdjacentDownRule,
|
|
92
|
+
TableAdjacentLeftRule,
|
|
93
|
+
TableAdjacentRightRule,
|
|
94
|
+
TableAdjacentRule,
|
|
95
|
+
TableAdjacentUpRule,
|
|
96
|
+
TableColspanRule,
|
|
97
|
+
TableHeaderChainRule,
|
|
98
|
+
TableLeftHeaderRule,
|
|
99
|
+
TableNoAboveRule,
|
|
100
|
+
TableNoBelowRule,
|
|
101
|
+
TableNoBorderRule,
|
|
102
|
+
TableNoLeftRule,
|
|
103
|
+
TableNoRightRule,
|
|
104
|
+
TableRowspanRule,
|
|
105
|
+
TableRule,
|
|
106
|
+
TableSameColumnRule,
|
|
107
|
+
TableSameRowRule,
|
|
108
|
+
TablesNumColsRule,
|
|
109
|
+
TablesNumRowsRule,
|
|
110
|
+
TablesValuesRule,
|
|
111
|
+
TableTopHeaderRule,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Text rules
|
|
115
|
+
from parse_bench.evaluation.metrics.parse.rules_text import ( # noqa: F401
|
|
116
|
+
BaselineRule,
|
|
117
|
+
TextOrderRule,
|
|
118
|
+
TextPresenceRule,
|
|
119
|
+
)
|
|
120
|
+
from parse_bench.evaluation.metrics.parse.rules_watermark import WatermarkRemovalRule # noqa: F401
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Test type definitions for parse evaluation."""
|
|
2
|
+
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestType(StrEnum):
|
|
7
|
+
"""Test types for parse evaluation."""
|
|
8
|
+
|
|
9
|
+
BASELINE = "baseline"
|
|
10
|
+
PRESENT = "present"
|
|
11
|
+
UNEXPECTED_SENTENCE = "unexpected_sentence"
|
|
12
|
+
UNEXPECTED_SENTENCE_PERCENT = "unexpected_sentence_percent"
|
|
13
|
+
TOO_MANY_SENTENCE_OCCURENCE = "too_many_sentence_occurence"
|
|
14
|
+
TOO_MANY_SENTENCE_OCCURENCE_PERCENT = "too_many_sentence_occurence_percent"
|
|
15
|
+
MISSING_SENTENCE = "missing_sentence"
|
|
16
|
+
MISSING_SENTENCE_PERCENT = "missing_sentence_percent"
|
|
17
|
+
MISSING_SPECIFIC_SENTENCE = "missing_specific_sentence"
|
|
18
|
+
UNEXPECTED_WORD = "unexpected_word"
|
|
19
|
+
UNEXPECTED_WORD_PERCENT = "unexpected_word_percent"
|
|
20
|
+
TOO_MANY_WORD_OCCURENCE = "too_many_word_occurence"
|
|
21
|
+
TOO_MANY_WORD_OCCURENCE_PERCENT = "too_many_word_occurence_percent"
|
|
22
|
+
MISSING_WORD = "missing_word"
|
|
23
|
+
MISSING_WORD_PERCENT = "missing_word_percent"
|
|
24
|
+
MISSING_SPECIFIC_WORD = "missing_specific_word"
|
|
25
|
+
# Keep for backward compatibility with earlier combined behavior
|
|
26
|
+
EXTRA_CONTENT = "extra_content"
|
|
27
|
+
ABSENT = "absent"
|
|
28
|
+
ORDER = "order"
|
|
29
|
+
TABLE = "table"
|
|
30
|
+
TABLES_VALUES = "tables_values"
|
|
31
|
+
TABLES_NUM_ROWS = "tables_num_rows"
|
|
32
|
+
TABLES_NUM_COLS = "tables_num_cols"
|
|
33
|
+
TABLE_MARKER_CELLS = "table_marker_cells"
|
|
34
|
+
MATH = "math"
|
|
35
|
+
# Table hierarchy test types
|
|
36
|
+
TABLE_COLSPAN = "table_colspan"
|
|
37
|
+
TABLE_ROWSPAN = "table_rowspan"
|
|
38
|
+
TABLE_SAME_ROW = "table_same_row"
|
|
39
|
+
TABLE_SAME_COLUMN = "table_same_column"
|
|
40
|
+
TABLE_HEADER_CHAIN = "table_header_chain" # Keep for backward compatibility
|
|
41
|
+
# New table adjacency test types
|
|
42
|
+
TABLE_ADJACENT_UP = "table_adjacent_up"
|
|
43
|
+
TABLE_ADJACENT_DOWN = "table_adjacent_down"
|
|
44
|
+
TABLE_ADJACENT_LEFT = "table_adjacent_left"
|
|
45
|
+
TABLE_ADJACENT_RIGHT = "table_adjacent_right"
|
|
46
|
+
TABLE_TOP_HEADER = "table_top_header"
|
|
47
|
+
TABLE_LEFT_HEADER = "table_left_header"
|
|
48
|
+
# Table border tests - verify absence of cells beyond boundaries
|
|
49
|
+
TABLE_NO_LEFT = "table_no_left"
|
|
50
|
+
TABLE_NO_RIGHT = "table_no_right"
|
|
51
|
+
TABLE_NO_ABOVE = "table_no_above"
|
|
52
|
+
TABLE_NO_BELOW = "table_no_below"
|
|
53
|
+
# Formatting test types - check if text has specific formatting
|
|
54
|
+
IS_UNDERLINE = "is_underline"
|
|
55
|
+
IS_NOT_UNDERLINE = "is_not_underline"
|
|
56
|
+
IS_BOLD = "is_bold"
|
|
57
|
+
IS_NOT_BOLD = "is_not_bold"
|
|
58
|
+
IS_STRIKEOUT = "is_strikeout"
|
|
59
|
+
IS_NOT_STRIKEOUT = "is_not_strikeout"
|
|
60
|
+
IS_ITALIC = "is_italic"
|
|
61
|
+
IS_NOT_ITALIC = "is_not_italic"
|
|
62
|
+
IS_MARK = "is_mark"
|
|
63
|
+
IS_NOT_MARK = "is_not_mark"
|
|
64
|
+
MARK_COLOR = "mark_color"
|
|
65
|
+
TEXT_COLOR = "text_color"
|
|
66
|
+
ABSENT_UNLESS_STRIKEOUT = "absent_unless_strikeout"
|
|
67
|
+
PRESENT_AS_STRIKEOUT = "present_as_strikeout"
|
|
68
|
+
IS_SUP = "is_sup"
|
|
69
|
+
IS_NOT_SUP = "is_not_sup"
|
|
70
|
+
IS_SUB = "is_sub"
|
|
71
|
+
IS_NOT_SUB = "is_not_sub"
|
|
72
|
+
IS_LATEX = "is_latex"
|
|
73
|
+
IS_NOT_LATEX = "is_not_latex"
|
|
74
|
+
IS_CODE_BLOCK = "is_code_block"
|
|
75
|
+
# Title / heading level test
|
|
76
|
+
IS_TITLE = "is_title"
|
|
77
|
+
TITLE_HIERARCHY_PERCENT = "title_hierarchy_percent"
|
|
78
|
+
HEADING_STRUCTURE = "heading_structure"
|
|
79
|
+
# List item nesting level test (CommonMark list semantics)
|
|
80
|
+
LIST_LEVEL = "list_level"
|
|
81
|
+
# Page header / footer tests
|
|
82
|
+
IS_HEADER = "is_header"
|
|
83
|
+
IS_FOOTER = "is_footer"
|
|
84
|
+
# Chart test types
|
|
85
|
+
CHART_DATA_POINT = "chart_data_point"
|
|
86
|
+
CHART_DATA_ARRAY_LABELS = "chart_data_array_labels"
|
|
87
|
+
CHART_DATA_ARRAY_DATA = "chart_data_array_data"
|
|
88
|
+
# Layout detection
|
|
89
|
+
LAYOUT = "layout"
|
|
90
|
+
# Digit bag rule
|
|
91
|
+
BAG_OF_DIGIT_PERCENT = "bag_of_digit_percent"
|
|
92
|
+
# Rotation check
|
|
93
|
+
ROTATE_CHECK = "rotate_check"
|
|
94
|
+
# Form field (key/value, checkbox, signature) extraction
|
|
95
|
+
FORM_FIELD = "form_field"
|
|
96
|
+
# Watermark suppression plus preservation of nearby document text.
|
|
97
|
+
WATERMARK_REMOVAL = "watermark_removal"
|
|
98
|
+
# Diagrams rendered as mermaid: graph semantics (nodes/edges), anchor relations, page-level count.
|
|
99
|
+
DIAGRAM_GRAPH = "diagram_graph"
|
|
100
|
+
DIAGRAM_EDGE = "diagram_edge"
|
|
101
|
+
DIAGRAM_COUNT = "diagram_count"
|
|
102
|
+
# Page furniture: running header, running footer, printed page number, and leakage into the body.
|
|
103
|
+
PAGE_DECORATION = "page_decoration"
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""Canonical plain-text projection for ParseBench Text Content rules."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
from bs4 import BeautifulSoup, Tag
|
|
8
|
+
|
|
9
|
+
_HTML_TABLE_TAG = re.compile(r"<table(?=[\s>])[^>]*>|</table\s*>", re.IGNORECASE)
|
|
10
|
+
_MARKDOWN_DELIMITER_CELL = re.compile(r"^:?-+:?$")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _html_table_spans(content: str) -> list[tuple[int, int]]:
|
|
14
|
+
"""Return matched top-level HTML table spans, recovering after malformed starts."""
|
|
15
|
+
stack: list[int] = []
|
|
16
|
+
matched: list[tuple[int, int]] = []
|
|
17
|
+
|
|
18
|
+
for tag in _HTML_TABLE_TAG.finditer(content):
|
|
19
|
+
if tag.group().lower().startswith("</"):
|
|
20
|
+
if stack:
|
|
21
|
+
matched.append((stack.pop(), tag.end()))
|
|
22
|
+
else:
|
|
23
|
+
stack.append(tag.start())
|
|
24
|
+
|
|
25
|
+
# A valid nested table is projected through its outer table. If an unmatched
|
|
26
|
+
# outer start precedes a later valid table, the later matched span survives.
|
|
27
|
+
return [
|
|
28
|
+
span
|
|
29
|
+
for span in matched
|
|
30
|
+
if not any(outer_start < span[0] and span[1] < outer_end for outer_start, outer_end in matched)
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _normalize_cell_text(cell: Tag) -> str:
|
|
35
|
+
return " ".join(cell.get_text(" ", strip=True).split())
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _html_table_lines(table: Tag) -> list[str]:
|
|
39
|
+
"""Emit captions, explicit rows, and orphan cells in document order."""
|
|
40
|
+
lines: list[str] = []
|
|
41
|
+
orphan_cells: list[str] = []
|
|
42
|
+
|
|
43
|
+
def flush_orphan_cells() -> None:
|
|
44
|
+
if orphan_cells:
|
|
45
|
+
lines.append("\t".join(orphan_cells))
|
|
46
|
+
orphan_cells.clear()
|
|
47
|
+
|
|
48
|
+
def visit(container: Tag) -> None:
|
|
49
|
+
for child in container.children:
|
|
50
|
+
if not isinstance(child, Tag):
|
|
51
|
+
continue
|
|
52
|
+
if child.name == "caption":
|
|
53
|
+
flush_orphan_cells()
|
|
54
|
+
text = _normalize_cell_text(child)
|
|
55
|
+
if text:
|
|
56
|
+
lines.append(text)
|
|
57
|
+
elif child.name == "tr":
|
|
58
|
+
flush_orphan_cells()
|
|
59
|
+
cells = [
|
|
60
|
+
_normalize_cell_text(cell)
|
|
61
|
+
for cell in child.find_all(["th", "td"])
|
|
62
|
+
if cell.find_parent("tr") is child
|
|
63
|
+
]
|
|
64
|
+
if cells:
|
|
65
|
+
lines.append("\t".join(cells))
|
|
66
|
+
elif child.name in {"th", "td"}:
|
|
67
|
+
text = _normalize_cell_text(child)
|
|
68
|
+
if text:
|
|
69
|
+
orphan_cells.append(text)
|
|
70
|
+
elif child.name != "table":
|
|
71
|
+
visit(child)
|
|
72
|
+
|
|
73
|
+
visit(table)
|
|
74
|
+
flush_orphan_cells()
|
|
75
|
+
return lines
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _canonicalize_html_table(table_html: str) -> str:
|
|
79
|
+
soup = BeautifulSoup(table_html, "html.parser")
|
|
80
|
+
table = soup.find("table")
|
|
81
|
+
if table is None:
|
|
82
|
+
return table_html
|
|
83
|
+
|
|
84
|
+
return "\n".join(_html_table_lines(table))
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _split_markdown_row(line: str) -> list[str]:
|
|
88
|
+
"""Split a Markdown table row without treating escaped/code pipes as separators."""
|
|
89
|
+
cells: list[str] = []
|
|
90
|
+
buffer: list[str] = []
|
|
91
|
+
code_ticks = 0
|
|
92
|
+
index = 0
|
|
93
|
+
|
|
94
|
+
while index < len(line):
|
|
95
|
+
char = line[index]
|
|
96
|
+
if char == "\\" and index + 1 < len(line):
|
|
97
|
+
buffer.extend((char, line[index + 1]))
|
|
98
|
+
index += 2
|
|
99
|
+
continue
|
|
100
|
+
if char == "`":
|
|
101
|
+
run_end = index + 1
|
|
102
|
+
while run_end < len(line) and line[run_end] == "`":
|
|
103
|
+
run_end += 1
|
|
104
|
+
run_length = run_end - index
|
|
105
|
+
if code_ticks == 0:
|
|
106
|
+
code_ticks = run_length
|
|
107
|
+
elif code_ticks == run_length:
|
|
108
|
+
code_ticks = 0
|
|
109
|
+
buffer.extend(line[index:run_end])
|
|
110
|
+
index = run_end
|
|
111
|
+
continue
|
|
112
|
+
if char == "|" and code_ticks == 0:
|
|
113
|
+
cells.append("".join(buffer).strip())
|
|
114
|
+
buffer = []
|
|
115
|
+
else:
|
|
116
|
+
buffer.append(char)
|
|
117
|
+
index += 1
|
|
118
|
+
|
|
119
|
+
cells.append("".join(buffer).strip())
|
|
120
|
+
if line.lstrip().startswith("|") and cells and not cells[0]:
|
|
121
|
+
cells = cells[1:]
|
|
122
|
+
if line.rstrip().endswith("|") and cells and not cells[-1]:
|
|
123
|
+
cells = cells[:-1]
|
|
124
|
+
return cells
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _is_markdown_delimiter(line: str) -> bool:
|
|
128
|
+
cells = _split_markdown_row(line)
|
|
129
|
+
return len(cells) > 0 and all(_MARKDOWN_DELIMITER_CELL.fullmatch(cell.replace(" ", "")) for cell in cells)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _canonicalize_markdown_tables(content: str) -> str:
|
|
133
|
+
lines = content.splitlines()
|
|
134
|
+
output: list[str] = []
|
|
135
|
+
index = 0
|
|
136
|
+
changed = False
|
|
137
|
+
|
|
138
|
+
while index < len(lines):
|
|
139
|
+
if (
|
|
140
|
+
index + 1 < len(lines)
|
|
141
|
+
and "|" in lines[index]
|
|
142
|
+
and "|" in lines[index + 1]
|
|
143
|
+
and _is_markdown_delimiter(lines[index + 1])
|
|
144
|
+
):
|
|
145
|
+
changed = True
|
|
146
|
+
table_lines = [lines[index]]
|
|
147
|
+
index += 2
|
|
148
|
+
while index < len(lines) and "|" in lines[index] and lines[index].strip():
|
|
149
|
+
table_lines.append(lines[index])
|
|
150
|
+
index += 1
|
|
151
|
+
output.extend("\t".join(_split_markdown_row(row)) for row in table_lines)
|
|
152
|
+
continue
|
|
153
|
+
|
|
154
|
+
output.append(lines[index])
|
|
155
|
+
index += 1
|
|
156
|
+
|
|
157
|
+
return "\n".join(output) if changed else content
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def canonicalize_tables_for_text_content(markdown: str) -> str:
|
|
161
|
+
"""Replace each table with one row-major text view for Text Content rules.
|
|
162
|
+
|
|
163
|
+
Every source cell is emitted once. Tabs separate cells and newlines separate
|
|
164
|
+
rows, preserving anchor continuity without adding alternate cell/row copies.
|
|
165
|
+
The caller retains the original Markdown for table and formatting metrics.
|
|
166
|
+
"""
|
|
167
|
+
if not markdown:
|
|
168
|
+
return markdown
|
|
169
|
+
|
|
170
|
+
spans = _html_table_spans(markdown)
|
|
171
|
+
for start, end in reversed(spans):
|
|
172
|
+
replacement = _canonicalize_html_table(markdown[start:end])
|
|
173
|
+
markdown = f"{markdown[:start]}\n{replacement}\n{markdown[end:]}"
|
|
174
|
+
|
|
175
|
+
return _canonicalize_markdown_tables(markdown)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Text similarity metric for comparing markdown against ground truth."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from parse_bench.evaluation.metrics.base import Metric
|
|
6
|
+
from parse_bench.schemas.evaluation import MetricValue
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TextSimilarityMetric(Metric):
|
|
10
|
+
"""Metric for comparing markdown text against ground truth using Levenshtein distance."""
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def name(self) -> str:
|
|
14
|
+
"""Return the name of this metric."""
|
|
15
|
+
return "text_similarity"
|
|
16
|
+
|
|
17
|
+
def compute(self, expected: str, actual: str, **kwargs: Any) -> MetricValue:
|
|
18
|
+
"""
|
|
19
|
+
Compute text similarity between expected and actual markdown.
|
|
20
|
+
|
|
21
|
+
:param expected: Expected markdown (ground truth)
|
|
22
|
+
:param actual: Actual markdown (from inference)
|
|
23
|
+
:param kwargs: Additional parameters (not used)
|
|
24
|
+
:return: MetricValue with similarity score (0.0 to 1.0)
|
|
25
|
+
"""
|
|
26
|
+
from autoevals.string import Levenshtein
|
|
27
|
+
|
|
28
|
+
if not expected and not actual:
|
|
29
|
+
return MetricValue(
|
|
30
|
+
metric_name=self.name,
|
|
31
|
+
value=1.0,
|
|
32
|
+
metadata={"note": "Both empty"},
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
if not expected or not actual:
|
|
36
|
+
return MetricValue(
|
|
37
|
+
metric_name=self.name,
|
|
38
|
+
value=0.0,
|
|
39
|
+
metadata={"note": "One is empty"},
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Use Levenshtein distance from autoevals
|
|
43
|
+
levenshtein = Levenshtein()
|
|
44
|
+
result = levenshtein(expected, actual)
|
|
45
|
+
|
|
46
|
+
# autoevals ``Levenshtein`` already returns a normalized score in
|
|
47
|
+
# [0, 1] (1.0 identical, 0.0 fully disjoint), so use it directly.
|
|
48
|
+
# Clamp only as a defensive guard against float drift — do NOT divide
|
|
49
|
+
# by 100 (that collapsed every value by two orders of magnitude, e.g.
|
|
50
|
+
# 0.76 -> 0.0076).
|
|
51
|
+
similarity = max(0.0, min(1.0, result.score))
|
|
52
|
+
|
|
53
|
+
return MetricValue(
|
|
54
|
+
metric_name=self.name,
|
|
55
|
+
value=similarity,
|
|
56
|
+
metadata={
|
|
57
|
+
"levenshtein_score": result.score,
|
|
58
|
+
"expected_length": len(expected),
|
|
59
|
+
"actual_length": len(actual),
|
|
60
|
+
},
|
|
61
|
+
)
|