docforge-zh 0.1.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.
Files changed (109) hide show
  1. docforge_zh-0.1.0/.gitattributes +5 -0
  2. docforge_zh-0.1.0/.github/ISSUE_TEMPLATE/benchmark_submission.yml +65 -0
  3. docforge_zh-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +36 -0
  4. docforge_zh-0.1.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
  5. docforge_zh-0.1.0/.github/workflows/ci.yml +56 -0
  6. docforge_zh-0.1.0/.github/workflows/publish.yml +36 -0
  7. docforge_zh-0.1.0/.gitignore +29 -0
  8. docforge_zh-0.1.0/CONTRIBUTING.md +36 -0
  9. docforge_zh-0.1.0/Dockerfile +20 -0
  10. docforge_zh-0.1.0/LICENSE +21 -0
  11. docforge_zh-0.1.0/PKG-INFO +188 -0
  12. docforge_zh-0.1.0/README.md +156 -0
  13. docforge_zh-0.1.0/benchmark/__init__.py +1 -0
  14. docforge_zh-0.1.0/benchmark/data/__init__.py +1 -0
  15. docforge_zh-0.1.0/benchmark/data/human/README.md +21 -0
  16. docforge_zh-0.1.0/benchmark/data/real/__init__.py +1 -0
  17. docforge_zh-0.1.0/benchmark/data/real/build_dataset.py +190 -0
  18. docforge_zh-0.1.0/benchmark/data/real/build_gt_b.py +127 -0
  19. docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/AGREEMENT.md +28 -0
  20. docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt/real-gov-001.json +31 -0
  21. docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt/real-gov-002.json +23 -0
  22. docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt/real-gov-003.json +41 -0
  23. docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt/real-scan-001.json +31 -0
  24. docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt/real-scan-002.json +28 -0
  25. docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt/real-table-001.json +28 -0
  26. docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt/real-table-002.json +28 -0
  27. docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt.b/real-gov-001.json +31 -0
  28. docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt.b/real-gov-002.json +23 -0
  29. docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt.b/real-gov-003.json +41 -0
  30. docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt.b/real-scan-001.json +31 -0
  31. docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt.b/real-scan-002.json +28 -0
  32. docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt.b/real-table-001.json +28 -0
  33. docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/gt.b/real-table-002.json +28 -0
  34. docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/inputs/jx-p1.pdf +0 -0
  35. docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/inputs/jx-p24-scan.pdf +370 -0
  36. docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/inputs/jx-p24.pdf +0 -0
  37. docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/inputs/jx-p26.pdf +0 -0
  38. docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/inputs/xwq-p1-scan.pdf +1759 -2
  39. docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/inputs/xwq-p1.pdf +0 -0
  40. docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/inputs/xwq-p11.pdf +0 -0
  41. docforge_zh-0.1.0/benchmark/data/real-v0.1-rc/manifest.json +62 -0
  42. docforge_zh-0.1.0/benchmark/data/synthetic/__init__.py +1 -0
  43. docforge_zh-0.1.0/benchmark/data/synthetic/generate.py +441 -0
  44. docforge_zh-0.1.0/benchmark/data/synthetic/gt/header-footer-001.json +19 -0
  45. docforge_zh-0.1.0/benchmark/data/synthetic/gt/headings-001.json +43 -0
  46. docforge_zh-0.1.0/benchmark/data/synthetic/gt/longtext-001.json +23 -0
  47. docforge_zh-0.1.0/benchmark/data/synthetic/gt/mixed-001.json +23 -0
  48. docforge_zh-0.1.0/benchmark/data/synthetic/gt/single-001.json +22 -0
  49. docforge_zh-0.1.0/benchmark/data/synthetic/gt/table-plain-001.json +17 -0
  50. docforge_zh-0.1.0/benchmark/data/synthetic/gt/table-span-001.json +17 -0
  51. docforge_zh-0.1.0/benchmark/data/synthetic/gt/table-span-002.json +17 -0
  52. docforge_zh-0.1.0/benchmark/data/synthetic/gt/twocol-001.json +25 -0
  53. docforge_zh-0.1.0/benchmark/data/synthetic/gt/twocol-002.json +25 -0
  54. docforge_zh-0.1.0/benchmark/data/synthetic/inputs/header-footer-001.pdf +0 -0
  55. docforge_zh-0.1.0/benchmark/data/synthetic/inputs/headings-001.pdf +0 -0
  56. docforge_zh-0.1.0/benchmark/data/synthetic/inputs/longtext-001.pdf +0 -0
  57. docforge_zh-0.1.0/benchmark/data/synthetic/inputs/mixed-001.pdf +0 -0
  58. docforge_zh-0.1.0/benchmark/data/synthetic/inputs/single-001.pdf +0 -0
  59. docforge_zh-0.1.0/benchmark/data/synthetic/inputs/table-plain-001.pdf +0 -0
  60. docforge_zh-0.1.0/benchmark/data/synthetic/inputs/table-span-001.pdf +0 -0
  61. docforge_zh-0.1.0/benchmark/data/synthetic/inputs/table-span-002.pdf +0 -0
  62. docforge_zh-0.1.0/benchmark/data/synthetic/inputs/twocol-001.pdf +0 -0
  63. docforge_zh-0.1.0/benchmark/data/synthetic/inputs/twocol-002.pdf +0 -0
  64. docforge_zh-0.1.0/benchmark/data/synthetic/manifest.json +67 -0
  65. docforge_zh-0.1.0/benchmark/leaderboard.py +80 -0
  66. docforge_zh-0.1.0/benchmark/metrics/__init__.py +1 -0
  67. docforge_zh-0.1.0/benchmark/metrics/heading_tree.py +41 -0
  68. docforge_zh-0.1.0/benchmark/metrics/order_and_text.py +74 -0
  69. docforge_zh-0.1.0/benchmark/metrics/teds.py +123 -0
  70. docforge_zh-0.1.0/benchmark/metrics/tree_edit.py +120 -0
  71. docforge_zh-0.1.0/benchmark/normalize.py +45 -0
  72. docforge_zh-0.1.0/benchmark/runner.py +245 -0
  73. docforge_zh-0.1.0/benchmark/schema.py +53 -0
  74. docforge_zh-0.1.0/docforge/__init__.py +6 -0
  75. docforge_zh-0.1.0/docforge/_pymupdf.py +10 -0
  76. docforge_zh-0.1.0/docforge/cli.py +305 -0
  77. docforge_zh-0.1.0/docforge/config.py +100 -0
  78. docforge_zh-0.1.0/docforge/engines/__init__.py +3 -0
  79. docforge_zh-0.1.0/docforge/engines/base.py +82 -0
  80. docforge_zh-0.1.0/docforge/engines/docforge_engine.py +31 -0
  81. docforge_zh-0.1.0/docforge/engines/markdown_engines.py +171 -0
  82. docforge_zh-0.1.0/docforge/errors.py +40 -0
  83. docforge_zh-0.1.0/docforge/logging_setup.py +51 -0
  84. docforge_zh-0.1.0/docforge/models.py +143 -0
  85. docforge_zh-0.1.0/docforge/pipeline.py +257 -0
  86. docforge_zh-0.1.0/docforge/py.typed +0 -0
  87. docforge_zh-0.1.0/docforge/server/__init__.py +1 -0
  88. docforge_zh-0.1.0/docforge/server/app.py +81 -0
  89. docforge_zh-0.1.0/docforge/stages/__init__.py +3 -0
  90. docforge_zh-0.1.0/docforge/stages/export.py +89 -0
  91. docforge_zh-0.1.0/docforge/stages/extract.py +126 -0
  92. docforge_zh-0.1.0/docforge/stages/layout.py +253 -0
  93. docforge_zh-0.1.0/docforge/stages/ocr.py +160 -0
  94. docforge_zh-0.1.0/docforge/stages/reading_order.py +135 -0
  95. docforge_zh-0.1.0/docforge/stages/table.py +267 -0
  96. docforge_zh-0.1.0/docforge/textutil.py +43 -0
  97. docforge_zh-0.1.0/docs/assets/demo.png +0 -0
  98. docforge_zh-0.1.0/docs/benchmark.md +98 -0
  99. docforge_zh-0.1.0/examples/demo.md +7 -0
  100. docforge_zh-0.1.0/pyproject.toml +56 -0
  101. docforge_zh-0.1.0/tests/conftest.py +20 -0
  102. docforge_zh-0.1.0/tests/integration/test_e2e.py +126 -0
  103. docforge_zh-0.1.0/tests/unit/test_config.py +32 -0
  104. docforge_zh-0.1.0/tests/unit/test_export_schema.py +94 -0
  105. docforge_zh-0.1.0/tests/unit/test_layout.py +123 -0
  106. docforge_zh-0.1.0/tests/unit/test_metrics.py +117 -0
  107. docforge_zh-0.1.0/tests/unit/test_reading_order.py +102 -0
  108. docforge_zh-0.1.0/tests/unit/test_table.py +159 -0
  109. docforge_zh-0.1.0/tests/unit/test_teds.py +135 -0
@@ -0,0 +1,5 @@
1
+ # 统一换行符:仓库内一律 LF,避免跨平台协作的 CRLF 噪声
2
+ * text=auto eol=lf
3
+ *.pdf binary
4
+ *.png binary
5
+ *.jpg binary
@@ -0,0 +1,65 @@
1
+ name: "Benchmark 提交(打榜)"
2
+ description: "提交你的解析引擎在 docforge 数据集上的评测结果"
3
+ labels: ["benchmark"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ 提交前请确认:
9
+ 1. 结果文件由 `docforge bench --engine <你的引擎>` 或你自己的引擎按 [提交规范](https://github.com/attnstack/docforge/blob/main/docs/benchmark.md) 生成;
10
+ 2. 已通过 `benchmark/schema.py` 的 schema 校验(校验不过的提交不会上榜);
11
+ 3. 请附上可复现命令与环境说明——不可复现的结果会被要求补充或关闭。
12
+ - type: input
13
+ id: engine
14
+ attributes:
15
+ label: "引擎名称与版本"
16
+ description: "如 docforge 0.1.0 / your-engine 1.2.0"
17
+ placeholder: "engine 0.1.0"
18
+ validations:
19
+ required: true
20
+ - type: dropdown
21
+ id: dataset
22
+ attributes:
23
+ label: "数据集版本"
24
+ options:
25
+ - docforge-zh-real-v0.1-rc
26
+ - docforge-zh-synthetic-v0.1
27
+ - 两者都跑了
28
+ - 其他(在说明里注明)
29
+ validations:
30
+ required: true
31
+ - type: textarea
32
+ id: results
33
+ attributes:
34
+ label: "指标结果"
35
+ description: "文本 1−CER / 阅读顺序 / 表格 TEDS / 标题树 / 综合,以及各指标覆盖率"
36
+ placeholder: |
37
+ text=0.87 order=0.98 table=0.67 heading=0.81 overall=0.83
38
+ coverage: text 7/7, table 4/7 ...
39
+ validations:
40
+ required: true
41
+ - type: textarea
42
+ id: submission
43
+ attributes:
44
+ label: "提交文件"
45
+ description: "附上提交 JSON(或 gist / 仓库链接)。单文件超限时给下载链接"
46
+ - type: textarea
47
+ id: repro
48
+ attributes:
49
+ label: "复现命令与环境"
50
+ placeholder: |
51
+ 命令:docforge bench --engine xxx --dataset ...
52
+ 环境:Ubuntu 22.04 / Python 3.11 / CPU,OCR 引擎及版本(如涉及扫描件)
53
+ validations:
54
+ required: true
55
+ - type: checkboxes
56
+ id: declare
57
+ attributes:
58
+ label: "声明"
59
+ options:
60
+ - label: "结果由评测工具生成,未手工修改指标数字"
61
+ required: true
62
+ - label: "提交 JSON 通过 schema 校验(benchmark/schema.py)"
63
+ required: true
64
+ - label: "同一结果未同时以多个 issue 重复提交"
65
+ required: true
@@ -0,0 +1,36 @@
1
+ name: "Bug 报告"
2
+ description: "解析结果错误、崩溃或性能问题"
3
+ labels: ["bug"]
4
+ body:
5
+ - type: textarea
6
+ id: what
7
+ attributes:
8
+ label: "问题现象"
9
+ description: "期望输出 vs 实际输出。表格结构错误请附原 PDF 截图与错误 HTML"
10
+ validations:
11
+ required: true
12
+ - type: textarea
13
+ id: repro
14
+ attributes:
15
+ label: "复现命令"
16
+ placeholder: "docforge convert in.pdf -o out.md --format both"
17
+ validations:
18
+ required: true
19
+ - type: textarea
20
+ id: input_desc
21
+ attributes:
22
+ label: "输入文件特征"
23
+ description: "页数、数字版/扫描件、是否含表格/多栏、来源类型(公文/论文/财报)。**涉及隐私请勿上传原文件,脱敏或裁剪后附图**"
24
+ - type: textarea
25
+ id: log
26
+ attributes:
27
+ label: "日志"
28
+ description: "加 --json-logs 运行的输出(异常栈务必完整)"
29
+ render: shell
30
+ - type: input
31
+ id: env
32
+ attributes:
33
+ label: "环境"
34
+ placeholder: "Windows 11 / Python 3.12 / docforge 0.1.0(是否安装 [ocr])"
35
+ validations:
36
+ required: true
@@ -0,0 +1,5 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: "使用问题 / 讨论"
4
+ url: "https://github.com/attnstack/docforge/discussions"
5
+ about: "解析效果调参、RAG 接入经验交流请到 Discussions"
@@ -0,0 +1,56 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: actions/setup-python@v5
14
+ with:
15
+ python-version: "3.11"
16
+ cache: pip
17
+ # OCR 依赖(paddle)不在 CI 安装:数百 MB 且非核心链路;扫描路径由本地测试覆盖
18
+ - name: Install
19
+ run: pip install -e ".[dev,bench]"
20
+ - name: Lint
21
+ run: ruff check docforge benchmark
22
+ - name: Seed dataset
23
+ run: docforge gen-seed
24
+ - name: Tests
25
+ run: pytest -q
26
+ - name: Benchmark smoke (deterministic gates)
27
+ run: |
28
+ docforge bench --engine docforge --engine pymupdf4llm --out results
29
+ python - <<'PY'
30
+ import json
31
+ for name, floor in (("docforge", 0.95), ("pymupdf4llm", 0.75)):
32
+ agg = json.load(open(f"results/{name}.json", encoding="utf-8"))["aggregate"]
33
+ overall = agg["overall"]
34
+ assert overall is not None and overall >= floor, f"{name} overall={overall} < {floor}"
35
+ print(f"{name}: overall={overall:.4f} (floor {floor})")
36
+ PY
37
+
38
+ leaderboard-page:
39
+ if: github.ref == 'refs/heads/main'
40
+ needs: test
41
+ runs-on: ubuntu-latest
42
+ permissions:
43
+ contents: write
44
+ steps:
45
+ - uses: actions/checkout@v4
46
+ - uses: actions/setup-python@v5
47
+ with:
48
+ python-version: "3.11"
49
+ cache: pip
50
+ - run: pip install -e ".[bench]"
51
+ - run: docforge gen-seed && docforge bench --engine docforge --engine pymupdf4llm --engine markitdown --out results
52
+ - run: docforge leaderboard --results results --out leaderboard/index.html
53
+ - uses: peaceiris/actions-gh-pages@v4
54
+ with:
55
+ github_token: ${{ secrets.GITHUB_TOKEN }}
56
+ publish_dir: ./leaderboard
@@ -0,0 +1,36 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ build:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: actions/setup-python@v5
14
+ with:
15
+ python-version: "3.11"
16
+ - run: pip install build
17
+ - name: Build sdist and wheel
18
+ run: python -m build
19
+ - uses: actions/upload-artifact@v4
20
+ with:
21
+ name: dist
22
+ path: dist/
23
+
24
+ publish:
25
+ needs: build
26
+ runs-on: ubuntu-latest
27
+ environment: pypi
28
+ permissions:
29
+ id-token: write # PyPI trusted publishing(OIDC),无需在 CI 里存 token
30
+ steps:
31
+ - uses: actions/download-artifact@v4
32
+ with:
33
+ name: dist
34
+ path: dist/
35
+ - name: Publish
36
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,29 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+
10
+ # 虚拟环境
11
+ .venv/
12
+ venv/
13
+
14
+ # 运行产物
15
+ badcases/
16
+ results*/
17
+ leaderboard/
18
+ probe*.pdf
19
+ .tmp/
20
+ *.log
21
+ examples/demo.json
22
+
23
+ # 标注工作区中的临时渲染页(保留 gt/pred json)
24
+ benchmark/data/human/*/pages/
25
+
26
+ # IDE
27
+ .idea/
28
+ .vscode/
29
+ 技术方案.md
@@ -0,0 +1,36 @@
1
+ # Contributing
2
+
3
+ 欢迎 PR。工程约定先读一遍,能省一轮 review。
4
+
5
+ ## 开发环境
6
+
7
+ ```bash
8
+ python -m venv .venv && source .venv/Scripts/activate # Windows Git Bash
9
+ pip install -e ".[dev,bench]"
10
+ ruff check docforge benchmark
11
+ pytest
12
+ ```
13
+
14
+ 可选:OCR 能力 `pip install "docforge[ocr]"`(PaddleOCR 较重,CI 不装,本地测试扫描链路时自行安装)。
15
+
16
+ ## 提交前检查
17
+
18
+ 1. `pytest` 全绿——核心算法(阅读顺序/表格/指标)的改动必须带新单测,覆盖正常/边界/异常三类输入;
19
+ 2. `ruff check` 无告警;
20
+ 3. 若改了指标实现或归一化:跑 `docforge bench --engine docforge`,在 PR 里贴出前后对比——种子集上任何数字变化都要能解释;
21
+ 4. 破坏性改动(schema、归一化规则、GT 格式)必须升数据集版本号。
22
+
23
+ ## 加一个基线引擎
24
+
25
+ 1. 在 `docforge/engines/` 新增引擎类,继承 `BaseEngine`,实现 `parse` 与 `missing_dependency`;
26
+ 2. 纯 markdown 输出的引擎直接继承 `_MarkdownEngineBase`,只实现 `_to_markdown`;
27
+ 3. 在 `engines/base.py::_register_defaults` 注册;
28
+ 4. 用 `docforge bench --engine your-engine` 跑一遍,确认提交能过 schema 校验。
29
+
30
+ ## 标注贡献
31
+
32
+ 见 [docs/benchmark.md](docs/benchmark.md) 第 4 节与 [benchmark/data/human/README.md](benchmark/data/human/README.md)。标注两个样本起即可提 PR,一致性报告附在 PR 描述里。
33
+
34
+ ## commit 风格
35
+
36
+ `<area>: <what>`(如 `table: fix colspan detection on partial borders`),正文说清 why。
@@ -0,0 +1,20 @@
1
+ # docforge 一键运行镜像:CPU 模式,含自研引擎 + OCR(PaddleOCR)+ 基线 + server
2
+ # 构建后直接 demo:docker run --rm -v "$PWD:/data" docforge convert /data/in.pdf -o /data/out.md
3
+ FROM python:3.11-slim
4
+
5
+ # paddleocr 依赖链需要:libgomp1(paddle)、libgl1/libglib2.0-0(opencv)
6
+ RUN apt-get update \
7
+ && apt-get install -y --no-install-recommends libgomp1 libgl1 libglib2.0-0 \
8
+ && rm -rf /var/lib/apt/lists/*
9
+
10
+ WORKDIR /app
11
+ COPY pyproject.toml README.md LICENSE ./
12
+ COPY docforge ./docforge
13
+ COPY benchmark ./benchmark
14
+
15
+ # PaddleOCR 模型文件不入镜像(几百 MB),首次运行自动下载到 ~/.paddleocr;
16
+ # 持久化:docker run -v docforge-models:/root/.paddleocr ...
17
+ RUN pip install --no-cache-dir ".[ocr,bench,server]"
18
+
19
+ ENTRYPOINT ["docforge"]
20
+ CMD ["--help"]
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 docforge contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,188 @@
1
+ Metadata-Version: 2.5
2
+ Name: docforge-zh
3
+ Version: 0.1.0
4
+ Summary: 中文文档解析引擎与公开 Benchmark:多栏/复杂表格 PDF -> LLM-ready Markdown 与带 bbox 的 JSON
5
+ Author: docforge contributors
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Keywords: benchmark,chinese,document-parsing,ocr,pdf,rag,reading-order,table-structure
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Topic :: Text Processing :: Markup :: Markdown
15
+ Requires-Python: >=3.11
16
+ Requires-Dist: jsonschema>=4.20
17
+ Requires-Dist: pymupdf>=1.24
18
+ Provides-Extra: bench
19
+ Requires-Dist: markitdown[pdf]>=0.1.1; extra == 'bench'
20
+ Requires-Dist: pymupdf4llm>=0.0.17; extra == 'bench'
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=8.0; extra == 'dev'
23
+ Requires-Dist: ruff>=0.5; extra == 'dev'
24
+ Provides-Extra: ocr
25
+ Requires-Dist: paddleocr<3.0,>=2.7; extra == 'ocr'
26
+ Requires-Dist: paddlepaddle<3.0,>=2.6; extra == 'ocr'
27
+ Provides-Extra: server
28
+ Requires-Dist: fastapi>=0.110; extra == 'server'
29
+ Requires-Dist: python-multipart>=0.0.9; extra == 'server'
30
+ Requires-Dist: uvicorn>=0.29; extra == 'server'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # docforge
34
+
35
+ **中文文档解析引擎与公开 Benchmark:把多栏、复杂表格、扫描件 PDF 转成 LLM-ready 的分层 Markdown 和带 bbox 可溯源的 JSON,并配套一个可复现的中文解析评测基准。**
36
+
37
+ <p align="center">
38
+ <a href="#-benchmark-榜单"><img alt="benchmark" src="https://img.shields.io/badge/benchmark-中文解析评测-blue"></a>
39
+ <a href="LICENSE"><img alt="license" src="https://img.shields.io/badge/license-MIT-green"></a>
40
+ <img alt="python" src="https://img.shields.io/badge/python-3.11%2B-informational">
41
+ <a href="https://github.com/attnstack/docforge/actions"><img alt="ci" src="https://img.shields.io/badge/CI-GitHub%20Actions-passing-brightgreen"></a>
42
+ </p>
43
+
44
+ <p align="center">
45
+ <img src="docs/assets/demo.png" alt="docforge:政府公文 PDF → Markdown 与跨行列表格结构还原" width="820">
46
+ </p>
47
+
48
+ ## 为什么是 docforge
49
+
50
+ - 文档解析是所有 RAG 的地基,但中文场景(扫描公文、财报合并单元格、双栏论文)没有公认的开源强方案;
51
+ - markitdown / PyMuPDF4LLM 这类文字层提取器**不做版面理解**:双栏论文会被逐行交错、合并单元格直接拍平;
52
+ - 更重要的是:**解析引擎会被追上,被引用的评测标准不会**。docforge 附带一个确定性指标、带提交校验的公开 benchmark,任何方案(包括 VLM 路线)都可以来打榜。
53
+
54
+ ## 30 秒 Quick Start
55
+
56
+ ```bash
57
+ # 1) 安装(核心依赖极轻:PyMuPDF + jsonschema)
58
+ pip install -e .
59
+ # 2) 生成合成种子数据集(程序化 PDF,GT 精确已知,用于打通评测链路)
60
+ docforge gen-seed
61
+ # 3) 解析一个多栏+表格的 PDF
62
+ docforge convert benchmark/data/synthetic/inputs/mixed-001.pdf -o out.md --format both
63
+ # 4) 在种子集上跑评测(自研引擎 + 可用基线),得到 report.md 与榜单页
64
+ docforge bench --engine docforge --engine pymupdf4llm
65
+ docforge leaderboard --results results/ --out leaderboard/index.html
66
+ ```
67
+
68
+ 扫描件 OCR 为可选能力:`pip install "docforge[ocr]"`(拉起 PaddleOCR,首次运行自动下载模型)。
69
+
70
+ Docker(含 OCR 全量依赖,镜像已实测可跑):
71
+
72
+ ```bash
73
+ docker build -t docforge .
74
+ docker run --rm -v "$PWD:/data" docforge convert /data/扫描件.pdf -o /data/out.md
75
+ ```
76
+
77
+ ## 特性
78
+
79
+ | 能力 | 状态 | 说明 |
80
+ |---|---|---|
81
+ | 数字版 PDF → Markdown / JSON | ✅ v0.1 | 文字层直提取,毫秒级;标题层级 + 跨页段落续接 |
82
+ | 双栏/多栏阅读顺序还原 | ✅ v0.1 | 通栏分段 → x 投影分栏 → 行带聚类,纯几何可单测 |
83
+ | 有框线表格结构还原 | ✅ v0.1 | 线段网格聚类 + union-find 合并,输出 rowspan/colspan HTML |
84
+ | 扫描件 OCR | 🔌 可选 | `docforge[ocr]`(PaddleOCR 2.x/3.x 双代适配),Windows 原生实测:扫描公文页文本 0.9931;扫描页表格结构 v0.2 |
85
+ | 公式 → LaTeX | ⏳ v0.2 | 指标已实现并入库,转换链路未启用(诚实声明,见「不足」) |
86
+ | Benchmark(4 指标 + schema 校验 + 榜单) | ✅ v0.1 | CER / TEDS / 阅读顺序 / 标题树,全部确定性指标,无 LLM judge |
87
+ | 人工标注工作流 | 🚧 进行中 | 预标注 + 双人一致性工具(`docforge annotate` / `docforge bench-agree`) |
88
+ | CLI / Docker / FastAPI | ✅ / ✅ / ✅ | 同步 `/convert`;异步任务队列 v0.2 |
89
+
90
+ ## 架构
91
+
92
+ ```mermaid
93
+ flowchart LR
94
+ A[PDF/图片] --> B{文字层?}
95
+ B -- 有 --> C[提取: 文字块/词/矢量线段]
96
+ B -- 无 --> O[OCR 可选依赖<br/>PaddleOCR]
97
+ O --> C2[OCR 行 -> 阅读排序 -> 段落合并]
98
+ C --> D[表格结构还原<br/>线段网格 + union-find]
99
+ C --> E[页眉页脚剔除<br/>跨页重复证据]
100
+ C --> F[标题层级<br/>字号加权中位数]
101
+ D --> G[阅读顺序还原<br/>通栏分段/投影分栏/行带聚类]
102
+ E --> G
103
+ F --> G
104
+ C2 --> G
105
+ G --> H[导出]
106
+ H --> I[分层 Markdown]
107
+ H --> J[带 bbox JSON<br/>page/bbox/reading_order/confidence]
108
+ I --> K[Benchmark Runner<br/>CER / TEDS / 顺序对 / 标题树]
109
+ J --> K
110
+ K --> L[结果 JSON -> 榜单页]
111
+ ```
112
+
113
+ 核心算法(值得读源码的部分):
114
+
115
+ - **阅读顺序**:[docforge/stages/reading_order.py](docforge/stages/reading_order.py) —— 通栏块先剥离再分栏;行带容差取 0.5×块高中位数;
116
+ - **表格结构**:[docforge/stages/table.py](docforge/stages/table.py) —— 局部边界缺失经 union-find 汇成跨行列区域;L 形合并的降级策略;
117
+ - **指标**:[benchmark/metrics/teds.py](benchmark/metrics/teds.py) 与 [benchmark/metrics/tree_edit.py](benchmark/metrics/tree_edit.py) —— Zhang-Shasha 树编辑距离自研实现 + 单测。
118
+
119
+ ## Benchmark 榜单
120
+
121
+ 两个数据集,口径均为确定性四指标(详见 [docs/benchmark.md](docs/benchmark.md)):
122
+
123
+ ### ① 真实公文集 `docforge-zh-real-v0.1-rc`(7 样本:5 数字页 + 2 仿真扫描页)
124
+
125
+ 来源:政府公开公文(依《著作权法》第五条可再分发,来源 URL 见 manifest)。GT 文本取自 PDF 文字层并逐块与原文程序化核对,结构(标题层级/表格跨行列)由标注员读渲染图核定;**已完成第二轮独立复核(仅依据渲染图重标),文本一致率 1.0000、结构零分歧([AGREEMENT.md](benchmark/data/real-v0.1-rc/AGREEMENT.md));单标注员两轮,升 v0.1 需第二位标注员**。
126
+
127
+ | 引擎 | 文本 1−CER ↑ | 阅读顺序 ↑ | 表格 TEDS ↑ | 标题树 ↑ | 综合 ↑ |
128
+ |---|---|---|---|---|---|
129
+ | docforge 0.1.0(含 OCR) | 0.8760 | 0.9762 | 0.6667 | 0.8095 | **0.8321** |
130
+ | PyMuPDF4LLM | 0.5331 | 1.0000 | 0.2044 | 0.5952 | 0.5832 |
131
+ | markitdown | 0.3576 | 0.9667 | 0.0000 | 0.3333 | 0.4144 |
132
+
133
+ > 复现:`docforge bench --engine docforge --engine pymupdf4llm --engine markitdown --dataset benchmark/data/real-v0.1-rc --out results-real`(docforge 需 `pip install "docforge[ocr]"`)
134
+ > 诚实解读(重要):
135
+ > - 数字页(5 样本)docforge 全指标 1.0:文本指标对"文字层提取类"引擎区分度低(GT 与引擎同源于文字层,1.0 是预期而非奇迹),区分度在结构——真实公文的跨行合并表格(rowspan=4×3 组、colspan 备注行)与"黑体/楷体同字号标题",两个基线均无法还原;
136
+ > - 扫描页(2 样本):扫描公文页 OCR 文本 0.9931、阅读顺序 1.0(真实识别质量,非文字层同源);扫描表格页 text 0.139 / table 0.0 是**已声明的 v0.1 边界**——扫描页无矢量线,表格结构不可还原,退化文本混入正文也被 GT 判罚,两项负分如实计入总分;
137
+ > - docforge 真实集数字基于 7 个样本,样本量小,不外推为整体性能主张;标题判定依赖公文格式先验(黑体/楷体/编号模式),对非公文版式的泛化未验证。
138
+
139
+ ### ② 合成种子集 `docforge-zh-synthetic-v0.1`(10 样本,评测链路自证与回归)
140
+
141
+ | 引擎 | 文本 1−CER ↑ | 阅读顺序 ↑ | 表格 TEDS ↑ | 标题树 ↑ | 综合 ↑ |
142
+ |---|---|---|---|---|---|
143
+ | docforge 0.1.0 | 1.0000 | 1.0000 | 1.0000 | 1.0000 | **1.0000** |
144
+ | PyMuPDF4LLM | 0.9773 | 1.0000 | 1.0000 | 0.6296 | 0.9017 |
145
+ | markitdown | 0.8389 | 0.9524 | 0.9726 | 0.4445 | 0.8021 |
146
+
147
+ > 复现:`docforge gen-seed && docforge bench --engine docforge --engine pymupdf4llm --engine markitdown && docforge leaderboard --results results --out leaderboard/index.html`(每样本耗时与逐项分数见 `results/report.md`;测试环境:Windows 11 / Python 3.12 / CPU)
148
+ > ⚠️ 合成集数字只证明"评测链路工作正常 + 引擎在构造样例上正确",**不构成对真实文档的性能主张**。
149
+ >
150
+ > 对对比结果的两点说明:
151
+ > - **PyMuPDF4LLM 在合成集表格 TEDS 上与我们打平**(1.0000):它能从 PDF 单元格拓扑恢复结构,只是不输出 rowspan/colspan 属性——在真实集的复杂表格上它掉到 0.2044,差距来自跨行列合并的真实复杂度;
152
+ > - markitdown 在双栏样本上文本分 0.32(逐行左右交错),这正是方案定位所针对的痛点,实测坐实。
153
+
154
+ ## Benchmark 设计要点(为什么可信)
155
+
156
+ 1. **确定性指标**:CER、TEDS(树编辑距离)、有序对准确率、标题树相似度——无 LLM judge,同输入必得同分;
157
+ 2. **归一化先约定**:NFKC → 小写 → 去空白,规则最小化并开源([benchmark/normalize.py](benchmark/normalize.py)),防止全半角/空白差异污染数字;
158
+ 3. **提交格式即契约**:JSON Schema 公布,CI 校验([benchmark/schema.py](benchmark/schema.py)),块粒度与对齐规则显式化;
159
+ 4. **标注质量自证**:双人独立标注 + `docforge bench-agree` 一致性报告;标注协议见 [docs/benchmark.md](docs/benchmark.md)。
160
+
161
+ ## 快速上手(开发者)
162
+
163
+ ```bash
164
+ git clone https://github.com/attnstack/docforge && cd docforge
165
+ python -m venv .venv && source .venv/Scripts/activate # Windows Git Bash
166
+ pip install -e ".[dev,bench]"
167
+ pytest # 全部单测 + 集成测试
168
+ ruff check docforge benchmark
169
+ ```
170
+
171
+ ## 已知不足(诚实清单)
172
+
173
+ - **无框线表格不支持**:v0.1 只承诺有框线表格;无框线需要检测模型(PP-Structure/SLANet),在 v0.2 路线图上;
174
+ - **公式只检测不转换**:LaTeX 识别(pix2tex 类)对中文混排准确率未实测,v0.1 不启用,避免给不可信数字;
175
+ - **OCR:Windows 原生已验证;WSL2/Docker + AMD 组合 `import paddleocr` 即崩溃**:实测 paddlepaddle 3.x 有 onednn 推理上游 bug(PIR 属性转换未实现);pin 的 2.6 稳定组合在 Windows 原生(含 AMD CPU)完整跑通扫描链路(真实扫描公文页文本 0.9931),但在 WSL2/Docker 的 Linux wheel 上**导入 paddleocr 即 SIGILL**(其原生库初始化在 AMD Zen 上的问题,已用 import 追踪定位到导入层)。`docforge[ocr]` 因此 pin 2.x;docforge 的懒加载设计保证数字版解析完全不触碰 paddle(数字路径在容器内正常),扫描需求在 WSL2+AMD 下请用 Windows 原生或 Intel 环境;
176
+ - **扫描页表格结构与标题不支持**:扫描页无矢量线(表格)无字号元数据(标题),v0.1 如实降级并在评测中计入负分,v0.2 用 PP-Structure 解决;
177
+ - **真实集规模小、单标注**:7 样本(5 数字 + 2 扫描仿真),单标注流程,双人标注与 Kappa/一致率报告在补;扩展到 50–100 页的标注协议见 docs/benchmark.md;
178
+ - **标题判定依赖公文格式先验**:黑体/楷体字体名 + 编号模式(一、/(一)/——)在公文上实测有效,对非公文中文版式的泛化未验证;
179
+ - **段落回流是启发式**:依赖首行缩进/字体连续信号,对无缩进的西文行级拆分场景可能欠合并;
180
+ - **PaddleOCR 模型文件不进镜像/仓库**:首次运行需联网下载(约几百 MB)。
181
+
182
+ ## Contributing
183
+
184
+ 见 [CONTRIBUTING.md](CONTRIBUTING.md)。benchmark 提交与榜单规则见 [docs/benchmark.md](docs/benchmark.md)。
185
+
186
+ ## License
187
+
188
+ [MIT](LICENSE)