docpluck 2.4.3__tar.gz → 2.4.4__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 (268) hide show
  1. {docpluck-2.4.3 → docpluck-2.4.4}/CHANGELOG.md +20 -0
  2. {docpluck-2.4.3 → docpluck-2.4.4}/PKG-INFO +1 -1
  3. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/__init__.py +1 -1
  4. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/extract_structured.py +46 -0
  5. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/figures/detect.py +23 -9
  6. {docpluck-2.4.3 → docpluck-2.4.4}/pyproject.toml +1 -1
  7. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_figure_detect.py +59 -0
  8. {docpluck-2.4.3 → docpluck-2.4.4}/.claude/skills/_project/lessons.md +0 -0
  9. {docpluck-2.4.3 → docpluck-2.4.4}/.claude/skills/docpluck-cleanup/SKILL.md +0 -0
  10. {docpluck-2.4.3 → docpluck-2.4.4}/.claude/skills/docpluck-deploy/SKILL.md +0 -0
  11. {docpluck-2.4.3 → docpluck-2.4.4}/.claude/skills/docpluck-qa/SKILL.md +0 -0
  12. {docpluck-2.4.3 → docpluck-2.4.4}/.claude/skills/docpluck-qa/references/benchmark-mode.md +0 -0
  13. {docpluck-2.4.3 → docpluck-2.4.4}/.claude/skills/docpluck-qa/references/check-11-hard-rules.md +0 -0
  14. {docpluck-2.4.3 → docpluck-2.4.4}/.claude/skills/docpluck-qa/references/check-13-escicheck-production.md +0 -0
  15. {docpluck-2.4.3 → docpluck-2.4.4}/.claude/skills/docpluck-qa/references/check-5-escicheck-library.md +0 -0
  16. {docpluck-2.4.3 → docpluck-2.4.4}/.claude/skills/docpluck-qa/references/check-6-escicheck-local-webapp.md +0 -0
  17. {docpluck-2.4.3 → docpluck-2.4.4}/.claude/skills/docpluck-qa/references/check-7-batch-smoke.md +0 -0
  18. {docpluck-2.4.3 → docpluck-2.4.4}/.claude/skills/docpluck-review/SKILL.md +0 -0
  19. {docpluck-2.4.3 → docpluck-2.4.4}/.github/workflows/publish.yml +0 -0
  20. {docpluck-2.4.3 → docpluck-2.4.4}/.github/workflows/test.yml +0 -0
  21. {docpluck-2.4.3 → docpluck-2.4.4}/.gitignore +0 -0
  22. {docpluck-2.4.3 → docpluck-2.4.4}/CLAUDE.md +0 -0
  23. {docpluck-2.4.3 → docpluck-2.4.4}/HANDOFF_SECTIONS_APP_INTEGRATION.md +0 -0
  24. {docpluck-2.4.3 → docpluck-2.4.4}/LESSONS.md +0 -0
  25. {docpluck-2.4.3 → docpluck-2.4.4}/LICENSE +0 -0
  26. {docpluck-2.4.3 → docpluck-2.4.4}/REPLY_FROM_DOCPLUCK_v1.4.5.md +0 -0
  27. {docpluck-2.4.3 → docpluck-2.4.4}/REPLY_FROM_DOCPLUCK_v1.5.0.md +0 -0
  28. {docpluck-2.4.3 → docpluck-2.4.4}/REQUEST_08_CHUNKING_ENDPOINT.md +0 -0
  29. {docpluck-2.4.3 → docpluck-2.4.4}/REQUEST_09_REFERENCE_LIST_NORMALIZATION.md +0 -0
  30. {docpluck-2.4.3 → docpluck-2.4.4}/TODO.md +0 -0
  31. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/__main__.py +0 -0
  32. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/batch.py +0 -0
  33. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/cli.py +0 -0
  34. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/extract.py +0 -0
  35. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/extract_docx.py +0 -0
  36. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/extract_html.py +0 -0
  37. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/extract_layout.py +0 -0
  38. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/figures/__init__.py +0 -0
  39. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/normalize.py +0 -0
  40. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/quality.py +0 -0
  41. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/render.py +0 -0
  42. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/sections/__init__.py +0 -0
  43. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/sections/annotators/__init__.py +0 -0
  44. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/sections/annotators/docx.py +0 -0
  45. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/sections/annotators/html.py +0 -0
  46. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/sections/annotators/pdf.py +0 -0
  47. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/sections/annotators/text.py +0 -0
  48. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/sections/blocks.py +0 -0
  49. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/sections/boundaries.py +0 -0
  50. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/sections/core.py +0 -0
  51. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/sections/taxonomy.py +0 -0
  52. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/sections/types.py +0 -0
  53. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/tables/__init__.py +0 -0
  54. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/tables/bbox_utils.py +0 -0
  55. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/tables/camelot_extract.py +0 -0
  56. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/tables/captions.py +0 -0
  57. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/tables/cell_cleaning.py +0 -0
  58. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/tables/cluster.py +0 -0
  59. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/tables/confidence.py +0 -0
  60. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/tables/detect.py +0 -0
  61. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/tables/render.py +0 -0
  62. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/tables/whitespace.py +0 -0
  63. {docpluck-2.4.3 → docpluck-2.4.4}/docpluck/version.py +0 -0
  64. {docpluck-2.4.3 → docpluck-2.4.4}/docs/BENCHMARKS.md +0 -0
  65. {docpluck-2.4.3 → docpluck-2.4.4}/docs/DESIGN.md +0 -0
  66. {docpluck-2.4.3 → docpluck-2.4.4}/docs/HANDOFF_2026-05-07_sections_strict_iteration.md +0 -0
  67. {docpluck-2.4.3 → docpluck-2.4.4}/docs/HANDOFF_2026-05-09_session_state_and_followups.md +0 -0
  68. {docpluck-2.4.3 → docpluck-2.4.4}/docs/HANDOFF_2026-05-09_unified_extraction_brainstorm.md +0 -0
  69. {docpluck-2.4.3 → docpluck-2.4.4}/docs/HANDOFF_2026-05-10_table_rendering_iteration.md +0 -0
  70. {docpluck-2.4.3 → docpluck-2.4.4}/docs/HANDOFF_2026-05-10_table_rendering_iteration_2.md +0 -0
  71. {docpluck-2.4.3 → docpluck-2.4.4}/docs/HANDOFF_2026-05-10_table_rendering_iteration_3.md +0 -0
  72. {docpluck-2.4.3 → docpluck-2.4.4}/docs/HANDOFF_2026-05-10_table_rendering_iteration_4.md +0 -0
  73. {docpluck-2.4.3 → docpluck-2.4.4}/docs/HANDOFF_2026-05-10_table_rendering_iteration_5.md +0 -0
  74. {docpluck-2.4.3 → docpluck-2.4.4}/docs/HANDOFF_2026-05-10_table_rendering_iteration_6.md +0 -0
  75. {docpluck-2.4.3 → docpluck-2.4.4}/docs/HANDOFF_2026-05-10_table_rendering_iteration_7.md +0 -0
  76. {docpluck-2.4.3 → docpluck-2.4.4}/docs/HANDOFF_2026-05-11_PROMOTE_SPIKE_TO_LIBRARY.md +0 -0
  77. {docpluck-2.4.3 → docpluck-2.4.4}/docs/HANDOFF_2026-05-11_table_rendering_iteration_8.md +0 -0
  78. {docpluck-2.4.3 → docpluck-2.4.4}/docs/HANDOFF_2026-05-11_visual_review_findings.md +0 -0
  79. {docpluck-2.4.3 → docpluck-2.4.4}/docs/HANDOFF_2026-05-12_phase2_101pdf_corpus.md +0 -0
  80. {docpluck-2.4.3 → docpluck-2.4.4}/docs/HANDOFF_2026-05-12_remaining_ui_and_chrome_verification.md +0 -0
  81. {docpluck-2.4.3 → docpluck-2.4.4}/docs/HANDOFF_2026-05-12_visual_verify_results.md +0 -0
  82. {docpluck-2.4.3 → docpluck-2.4.4}/docs/NORMALIZATION.md +0 -0
  83. {docpluck-2.4.3 → docpluck-2.4.4}/docs/README.md +0 -0
  84. {docpluck-2.4.3 → docpluck-2.4.4}/docs/TRIAGE_2026-05-10_corpus_assessment.md +0 -0
  85. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/2026-05-06-section-identification.md +0 -0
  86. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/2026-05-06-table-extraction.md +0 -0
  87. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/2026-05-07-sections-strict-iteration-progress.md +0 -0
  88. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/2026-05-08-unified-extraction-phase-0-splice-spike.md +0 -0
  89. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/sections-deferred-items.md +0 -0
  90. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/sections-issues-backlog.md +0 -0
  91. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/2026-05-07_spot-01_apa.md +0 -0
  92. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/2026-05-07_spot-02_pattern-A-shipped.md +0 -0
  93. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/2026-05-08_spot-final_all-styles.md +0 -0
  94. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/experiments/COMPARISON.md +0 -0
  95. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-a/korbmacher_table1.md +0 -0
  96. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-a/option-a.py +0 -0
  97. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-a/ziano_table1.md +0 -0
  98. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-b/korbmacher_notes_raw.txt +0 -0
  99. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-b/korbmacher_table1.md +0 -0
  100. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-b/notes.md +0 -0
  101. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-b/option-b.py +0 -0
  102. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-b/ziano_notes_raw.txt +0 -0
  103. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-b/ziano_table1.md +0 -0
  104. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-c/korbmacher_table1.md +0 -0
  105. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-c/notes.md +0 -0
  106. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-c/option-c.py +0 -0
  107. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-c/sample-pdftotext-bbox.html +0 -0
  108. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-c/ziano_table1.md +0 -0
  109. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-d/korbmacher_table1.md +0 -0
  110. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-d/notes.md +0 -0
  111. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-d/option-d.py +0 -0
  112. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-d/ziano_table1.md +0 -0
  113. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/korbmacher_2022_kruger_bbox.html +0 -0
  114. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/korbmacher_bbox.html +0 -0
  115. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/korbmacher_table1.md +0 -0
  116. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/option-e.py +0 -0
  117. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/sample-bbox.html +0 -0
  118. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/ziano_2021_joep_bbox.html +0 -0
  119. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/ziano_bbox.html +0 -0
  120. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/ziano_table1.md +0 -0
  121. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/html-fallback-demo.md +0 -0
  122. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs/chandrashekar_2023_mp.err +0 -0
  123. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs/chandrashekar_2023_mp.md +0 -0
  124. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs/efendic_2022_affect.err +0 -0
  125. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs/efendic_2022_affect.md +0 -0
  126. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs/ieee_access_2.err +0 -0
  127. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs/ieee_access_2.md +0 -0
  128. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs/ip_feldman_2025_pspb.err +0 -0
  129. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs/ip_feldman_2025_pspb.md +0 -0
  130. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs/korbmacher_2022_kruger.err +0 -0
  131. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs/korbmacher_2022_kruger.md +0 -0
  132. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs/nat_comms_1.err +0 -0
  133. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs/nat_comms_1.md +0 -0
  134. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs/ziano_2021_joep.err +0 -0
  135. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs/ziano_2021_joep.md +0 -0
  136. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/am_sociol_rev_3.err +0 -0
  137. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/am_sociol_rev_3.md +0 -0
  138. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/amc_1.err +0 -0
  139. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/amc_1.md +0 -0
  140. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/amj_1.err +0 -0
  141. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/amj_1.md +0 -0
  142. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/amle_1.err +0 -0
  143. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/amle_1.md +0 -0
  144. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ar_apa_j_jesp_2009_12_010.err +0 -0
  145. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ar_apa_j_jesp_2009_12_010.md +0 -0
  146. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ar_royal_society_rsos_140066.err +0 -0
  147. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ar_royal_society_rsos_140066.md +0 -0
  148. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ar_royal_society_rsos_140072.err +0 -0
  149. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ar_royal_society_rsos_140072.md +0 -0
  150. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/bjps_1.err +0 -0
  151. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/bjps_1.md +0 -0
  152. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/chan_feldman_2025_cogemo.err +0 -0
  153. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/chan_feldman_2025_cogemo.md +0 -0
  154. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/chen_2021_jesp.err +0 -0
  155. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/chen_2021_jesp.md +0 -0
  156. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/demography_1.err +0 -0
  157. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/demography_1.md +0 -0
  158. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ieee_access_3.err +0 -0
  159. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ieee_access_3.md +0 -0
  160. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ieee_access_4.err +0 -0
  161. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ieee_access_4.md +0 -0
  162. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/jama_open_1.err +0 -0
  163. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/jama_open_1.md +0 -0
  164. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/jama_open_2.err +0 -0
  165. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/jama_open_2.md +0 -0
  166. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/jmf_1.err +0 -0
  167. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/jmf_1.md +0 -0
  168. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/nat_comms_2.err +0 -0
  169. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/nat_comms_2.md +0 -0
  170. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/sci_rep_1.err +0 -0
  171. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/sci_rep_1.md +0 -0
  172. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/social_forces_1.err +0 -0
  173. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/social_forces_1.md +0 -0
  174. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/papers.md +0 -0
  175. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/report.md +0 -0
  176. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/splice_spike.py +0 -0
  177. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/plans/spot-checks/splice-spike/test_splice_spike.py +0 -0
  178. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/specs/2026-04-27-request-09-reference-normalization-design.md +0 -0
  179. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/specs/2026-05-06-section-identification-design.md +0 -0
  180. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/specs/2026-05-06-table-extraction-design.md +0 -0
  181. {docpluck-2.4.3 → docpluck-2.4.4}/docs/superpowers/specs/2026-05-08-unified-extraction-design.md +0 -0
  182. {docpluck-2.4.3 → docpluck-2.4.4}/scripts/verify_corpus.py +0 -0
  183. {docpluck-2.4.3 → docpluck-2.4.4}/scripts/verify_corpus_full.py +0 -0
  184. {docpluck-2.4.3 → docpluck-2.4.4}/tests/__init__.py +0 -0
  185. {docpluck-2.4.3 → docpluck-2.4.4}/tests/conftest.py +0 -0
  186. {docpluck-2.4.3 → docpluck-2.4.4}/tests/fixtures/__init__.py +0 -0
  187. {docpluck-2.4.3 → docpluck-2.4.4}/tests/fixtures/sections/__init__.py +0 -0
  188. {docpluck-2.4.3 → docpluck-2.4.4}/tests/fixtures/sections/builders.py +0 -0
  189. {docpluck-2.4.3 → docpluck-2.4.4}/tests/fixtures/structured/.gitkeep +0 -0
  190. {docpluck-2.4.3 → docpluck-2.4.4}/tests/fixtures/structured/MANIFEST.json +0 -0
  191. {docpluck-2.4.3 → docpluck-2.4.4}/tests/fixtures/structured/README.md +0 -0
  192. {docpluck-2.4.3 → docpluck-2.4.4}/tests/golden/sections/apa_multi_study_pdf.json +0 -0
  193. {docpluck-2.4.3 → docpluck-2.4.4}/tests/golden/sections/apa_single_study_pdf.json +0 -0
  194. {docpluck-2.4.3 → docpluck-2.4.4}/tests/golden/sections/html_real_headings.json +0 -0
  195. {docpluck-2.4.3 → docpluck-2.4.4}/tests/snapshots/amj_lattice.txt +0 -0
  196. {docpluck-2.4.3 → docpluck-2.4.4}/tests/snapshots/apa_chan_feldman_lineless.txt +0 -0
  197. {docpluck-2.4.3 → docpluck-2.4.4}/tests/snapshots/apa_chen_jesp_lineless.txt +0 -0
  198. {docpluck-2.4.3 → docpluck-2.4.4}/tests/snapshots/apa_efendic_affect.txt +0 -0
  199. {docpluck-2.4.3 → docpluck-2.4.4}/tests/snapshots/apa_ip_feldman_pspb.txt +0 -0
  200. {docpluck-2.4.3 → docpluck-2.4.4}/tests/snapshots/bmc_lattice.txt +0 -0
  201. {docpluck-2.4.3 → docpluck-2.4.4}/tests/snapshots/ieee_figure_heavy.txt +0 -0
  202. {docpluck-2.4.3 → docpluck-2.4.4}/tests/snapshots/ieee_lattice.txt +0 -0
  203. {docpluck-2.4.3 → docpluck-2.4.4}/tests/snapshots/jama_lattice.txt +0 -0
  204. {docpluck-2.4.3 → docpluck-2.4.4}/tests/snapshots/nat_comms_figure_only.txt +0 -0
  205. {docpluck-2.4.3 → docpluck-2.4.4}/tests/snapshots/nature_minimal_rule.txt +0 -0
  206. {docpluck-2.4.3 → docpluck-2.4.4}/tests/snapshots/scirep_minimal_rule.txt +0 -0
  207. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_bbox_utils.py +0 -0
  208. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_benchmark_docx_html.py +0 -0
  209. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_caption_regex.py +0 -0
  210. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_cli_sections.py +0 -0
  211. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_cli_structured.py +0 -0
  212. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_confidence.py +0 -0
  213. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_corpus_smoke.py +0 -0
  214. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_d5_normalization_audit.py +0 -0
  215. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_edge_cases.py +0 -0
  216. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_extract_docx.py +0 -0
  217. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_extract_filter_sugar.py +0 -0
  218. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_extract_html.py +0 -0
  219. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_extract_layout.py +0 -0
  220. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_extract_pdf_structured.py +0 -0
  221. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_extraction.py +0 -0
  222. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_f0_table_region_aware.py +0 -0
  223. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_fixtures_manifest.py +0 -0
  224. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_lattice_cluster.py +0 -0
  225. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_metaesci_followups.py +0 -0
  226. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_normalization.py +0 -0
  227. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_normalize_f0_footnote_strip.py +0 -0
  228. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_normalize_layout_param.py +0 -0
  229. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_normalize_report_layout_fields.py +0 -0
  230. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_normalize_v18_strips.py +0 -0
  231. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_quality.py +0 -0
  232. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_render.py +0 -0
  233. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_render_html.py +0 -0
  234. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_request_09_reference_normalization.py +0 -0
  235. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_sections_boundaries.py +0 -0
  236. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_sections_boundary_truncation.py +0 -0
  237. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_sections_core_partition.py +0 -0
  238. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_sections_docx_annotator.py +0 -0
  239. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_sections_extract_text.py +0 -0
  240. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_sections_footnote_section.py +0 -0
  241. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_sections_golden.py +0 -0
  242. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_sections_html_annotator.py +0 -0
  243. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_sections_pdf_annotator.py +0 -0
  244. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_sections_public_api.py +0 -0
  245. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_sections_real_corpus.py +0 -0
  246. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_sections_taxonomy.py +0 -0
  247. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_sections_text_annotator.py +0 -0
  248. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_sections_types.py +0 -0
  249. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_sections_unit_corpus.py +0 -0
  250. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_sections_v161_coalesce.py +0 -0
  251. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_sections_v161_subheadings.py +0 -0
  252. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_sections_v161_taxonomy.py +0 -0
  253. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_sections_v161_text_annotator.py +0 -0
  254. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_sections_version.py +0 -0
  255. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_smoke_fixtures.py +0 -0
  256. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_structured_result_type.py +0 -0
  257. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_structured_types.py +0 -0
  258. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_structured_version.py +0 -0
  259. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_table_detect.py +0 -0
  260. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_tables_cell_cleaning.py +0 -0
  261. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_text_mode.py +0 -0
  262. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_v23_1_fixes.py +0 -0
  263. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_v23_bug_fixes.py +0 -0
  264. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_v23_post_corpus.py +0 -0
  265. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_v23_post_corpus_v2.py +0 -0
  266. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_v2_backwards_compat.py +0 -0
  267. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_v2_top_level_exports.py +0 -0
  268. {docpluck-2.4.3 → docpluck-2.4.4}/tests/test_whitespace_cluster.py +0 -0
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.4.4] — 2026-05-13
4
+
5
+ Bug fix on v2.4.3's caption-trim feature + extension to a second chart-data signature.
6
+
7
+ ### Bug fix
8
+
9
+ 1. **`docpluck/extract_structured.py::_extract_caption_text`** — v2.4.3's `_trim_caption_at_chart_data` was added to `docpluck/figures/detect.py::_full_caption_text`, but the live render pipeline never calls that function — figure captions are built in `extract_structured.py::_extract_caption_text` (which `_figure_from_caption` calls). v2.4.3's caption-trim was therefore a no-op on real renders despite its tests passing in isolation. v2.4.4 applies the trim to `_extract_caption_text` for `kind == "figure"` captions, so the trim actually fires during `render_pdf_to_markdown(pdf_bytes)`. Verified by manual render of `jama_open_6` (caption 400 chars → 47 chars) and `jama_open_3` (405 → 208 chars).
10
+
11
+ ### Enhancement
12
+
13
+ 2. **`docpluck/extract_structured.py::_trim_caption_at_chart_data`** — extended with a second chart-data signature: a run of 5+ short (1–4 digit) numeric tokens separated only by whitespace. Catches axis-tick label sequences (``0 5 10 15 20``) and stacked column values (``340 321 280 5 270``) that the 6-digit-run rule didn't see on charts with small-magnitude data. The two signatures are evaluated jointly; the earlier match in the caption wins so the caption is trimmed at the start of the chart data, not partway through it. Same conservative gates as before (caption ≥ 150 chars, surviving text ≥ 40 chars). Affects most JAMA Network Open Kaplan-Meier and Sci Rep / BMC clinical-trial papers — caption length drops from 400-char hard cap to ~150 chars of real prose.
14
+
15
+ ### Bumps
16
+
17
+ - `__version__`: `2.4.3` → `2.4.4`. Patch — figure-caption truncation is now real and broader.
18
+
19
+ ### Tests
20
+
21
+ 3 new tests in `tests/test_figure_detect.py` (tick-run truncation, prose-with-inline-numbers no-op, earlier-of-two-signatures priority).
22
+
3
23
  ## [2.4.3] — 2026-05-13
4
24
 
5
25
  Same-day follow-up. Two preventative improvements aimed at quality issues that didn't trip the verifier tags but were visible in rendered output:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: docpluck
3
- Version: 2.4.3
3
+ Version: 2.4.4
4
4
  Summary: PDF, DOCX, and HTML text extraction and normalization for academic papers
5
5
  Project-URL: Homepage, https://github.com/giladfeldman/docpluck
6
6
  Project-URL: Documentation, https://github.com/giladfeldman/docpluck/tree/main/docs
@@ -71,7 +71,7 @@ from .figures import Figure
71
71
  from .extract_structured import TABLE_EXTRACTION_VERSION, StructuredResult, extract_pdf_structured
72
72
  from .render import render_pdf_to_markdown
73
73
 
74
- __version__ = "2.4.3"
74
+ __version__ = "2.4.4"
75
75
  __author__ = "Gilad Feldman"
76
76
  __license__ = "MIT"
77
77
 
@@ -332,11 +332,57 @@ def _extract_caption_text(
332
332
  # Re-prefix the label if stripping ate it.
333
333
  if cap.label and not snippet.startswith(cap.label):
334
334
  snippet = f"{cap.label}. {snippet}".strip()
335
+ # v2.4.4: trim chart-data appendage from figure captions (axis-tick
336
+ # sequences, raw bar-chart values pdftotext joined inline into the
337
+ # caption paragraph). For tables the appendage is usually the next-
338
+ # row continuation so skip — the caption hard-cap at 400 below
339
+ # bounds it.
340
+ if cap.kind == "figure":
341
+ snippet = _trim_caption_at_chart_data(snippet)
335
342
  if len(snippet) > 400:
336
343
  snippet = snippet[:400].rsplit(" ", 1)[0] + "…"
337
344
  return snippet
338
345
 
339
346
 
347
+ # v2.4.4: shared chart-data trim, duplicated logic from
348
+ # ``docpluck.figures.detect._trim_caption_at_chart_data`` so this module
349
+ # doesn't import from ``figures.detect`` (which has its own layout-channel
350
+ # dependencies). Two signatures of pdftotext-joined chart data:
351
+ # 1. Run of 6+ consecutive digits — flowchart counts, row IDs.
352
+ # 2. Run of 5+ short (1–4 digit) numeric tokens separated only by
353
+ # whitespace — axis-tick label sequences.
354
+ _CHART_DATA_DIGIT_RUN_RE_STRUCT = re.compile(r"\b\d{6,}\b")
355
+ _CHART_DATA_TICK_RUN_RE_STRUCT = re.compile(r"(?:\b\d{1,4}\b[ \t]+){5,}")
356
+
357
+
358
+ def _trim_caption_at_chart_data(caption: str) -> str:
359
+ """Truncate a caption when it transitions from prose to chart-data.
360
+
361
+ Conservative: only fires when caption ≥ 150 chars AND the surviving
362
+ trimmed text is ≥ 40 chars. The two regex signatures catch
363
+ complementary chart-data patterns (large counts and small axis-tick
364
+ sequences); the earlier match wins.
365
+ """
366
+ if not caption or len(caption) < 150:
367
+ return caption
368
+ candidates: list[int] = []
369
+ m1 = _CHART_DATA_DIGIT_RUN_RE_STRUCT.search(caption)
370
+ if m1 is not None:
371
+ candidates.append(m1.start())
372
+ m2 = _CHART_DATA_TICK_RUN_RE_STRUCT.search(caption)
373
+ if m2 is not None:
374
+ candidates.append(m2.start())
375
+ if not candidates:
376
+ return caption
377
+ cut = min(candidates)
378
+ while cut > 0 and not caption[cut - 1].isspace():
379
+ cut -= 1
380
+ trimmed = caption[:cut].rstrip(" ,;:")
381
+ if len(trimmed) < 40:
382
+ return caption
383
+ return trimmed
384
+
385
+
340
386
  def _isolated_table_from_caption(
341
387
  cap: CaptionMatch,
342
388
  raw_text: str,
@@ -146,6 +146,14 @@ def _full_caption_text(raw_text: str, cap: CaptionMatch) -> str:
146
146
  # that pdftotext joined chart data (raw bar-chart values, participant counts,
147
147
  # row IDs) into the caption.
148
148
  _CHART_DATA_DIGIT_RUN_RE = re.compile(r"\b\d{6,}\b")
149
+ # A run of 5+ short numeric tokens (1–4 digits each) separated only by
150
+ # whitespace is a v2.4.4 signal — captures axis-tick label sequences
151
+ # (``0 5 10 15 20``) and stacked column values (``340 321 280 5 270``)
152
+ # that the 6-digit rule misses on charts with small-magnitude data.
153
+ # Real captions reference numbers via prose ("with n = 1234 participants",
154
+ # "p < .001"), so digit tokens are interleaved with words rather than
155
+ # stacked five-in-a-row.
156
+ _CHART_DATA_TICK_RUN_RE = re.compile(r"(?:\b\d{1,4}\b[ \t]+){5,}")
149
157
 
150
158
 
151
159
  def _trim_caption_at_chart_data(caption: str) -> str:
@@ -161,21 +169,27 @@ def _trim_caption_at_chart_data(caption: str) -> str:
161
169
  where the real caption is "Flowchart of Study Sample Selection" and the
162
170
  rest is chart data values.
163
171
 
164
- Strategy: locate the first run of 6+ consecutive digits (the signature
165
- of chart data counts or row IDs that no real caption sentence would
166
- contain). Truncate the caption just before that run, falling back to
167
- the word boundary so we don't end mid-word.
172
+ v2.4.4: two complementary signatures are scanned (see module-level
173
+ constants); the *earlier* match in the caption wins so the caption is
174
+ trimmed at the start of the chart data, not partway through it.
168
175
 
169
176
  Conservative: only fires when the caption is ≥ 150 chars (real short
170
- captions almost never have chart-data appendage), and only when the
171
- surviving trimmed caption is ≥ 40 chars after the label.
177
+ captions almost never have a chart-data appendage), and only when the
178
+ surviving trimmed caption is ≥ 40 chars (sanity check protects against
179
+ edge cases where the digit run lands near the label).
172
180
  """
173
181
  if not caption or len(caption) < 150:
174
182
  return caption
175
- m = _CHART_DATA_DIGIT_RUN_RE.search(caption)
176
- if m is None:
183
+ candidates: list[int] = []
184
+ m1 = _CHART_DATA_DIGIT_RUN_RE.search(caption)
185
+ if m1 is not None:
186
+ candidates.append(m1.start())
187
+ m2 = _CHART_DATA_TICK_RUN_RE.search(caption)
188
+ if m2 is not None:
189
+ candidates.append(m2.start())
190
+ if not candidates:
177
191
  return caption
178
- cut = m.start()
192
+ cut = min(candidates)
179
193
  # Walk back to the previous word boundary.
180
194
  while cut > 0 and not caption[cut - 1].isspace():
181
195
  cut -= 1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "docpluck"
7
- version = "2.4.3"
7
+ version = "2.4.4"
8
8
  description = "PDF, DOCX, and HTML text extraction and normalization for academic papers"
9
9
  readme = "docs/README.md"
10
10
  requires-python = ">=3.10"
@@ -159,3 +159,62 @@ def test_trim_caption_keeps_minimum_post_label_content():
159
159
  out = _trim_caption_at_chart_data(short_pre_label)
160
160
  # Sanity check fires; return original.
161
161
  assert out == short_pre_label
162
+
163
+
164
+ # v2.4.4: caption truncation extended to short-token tick runs (5+ short
165
+ # numeric tokens in a row — axis-tick label sequences from charts).
166
+
167
+
168
+ def test_trim_caption_at_tick_run_truncates_axis_labels():
169
+ """v2.4.4: detect chart axis-tick sequences (5+ short numeric tokens
170
+ separated only by whitespace) — jama_open_3-style Kaplan-Meier
171
+ captions absorb gridline values like ``0 0 5 10 15`` that the 6-digit
172
+ rule didn't catch."""
173
+ from docpluck.figures.detect import _trim_caption_at_chart_data
174
+ cap = (
175
+ "Figure 1. Unadjusted Kaplan-Meier Curves Across Groups With "
176
+ "Different Objective Sleep Duration for All-Cause Mortality 100 "
177
+ "90 Survival probability, % 80 70 Sleep duration 60 seven hours "
178
+ "6 to 7 hours 50 5 to 6 hours less than 5 hours 0 0 5 10 15 "
179
+ "Follow-up time y No at risk Sleep duration seven hours 340 321 "
180
+ "280 5 Sleep duration"
181
+ )
182
+ out = _trim_caption_at_chart_data(cap)
183
+ assert "0 0 5 10 15" not in out
184
+ # Trim should preserve the prose lead-in.
185
+ assert out.startswith("Figure 1. Unadjusted Kaplan-Meier Curves")
186
+
187
+
188
+ def test_trim_caption_preserves_legitimate_prose_with_inline_numbers():
189
+ """Real caption prose references numbers in stats ('n = 1234', 'p < .001'),
190
+ but each number is followed by a word — not 5+ stacked numerics in a row."""
191
+ from docpluck.figures.detect import _trim_caption_at_chart_data
192
+ cap = (
193
+ "Figure 2. Mean reaction times across the four experimental "
194
+ "conditions, with n = 1234 participants total (95% CI [120.5, "
195
+ "180.3] ms for condition A; 95% CI [110.2, 175.4] for condition "
196
+ "B). Significant differences observed at p < .001 between paired "
197
+ "conditions in all 4 contrasts of interest, as predicted."
198
+ )
199
+ out = _trim_caption_at_chart_data(cap)
200
+ assert out == cap
201
+
202
+
203
+ def test_trim_caption_picks_earliest_match_across_both_rules():
204
+ """When both the 6-digit-run and the 5-token-tick rules match,
205
+ truncate at the earlier offset so we don't keep chart data past the
206
+ first signal."""
207
+ from docpluck.figures.detect import _trim_caption_at_chart_data
208
+ # Tick run appears first; 6-digit run appears later.
209
+ cap = (
210
+ "Figure 3. Bar plot of conditions A through F across the years "
211
+ "of interest 2020 2021 2022 2023 2024 2025 with later analytic "
212
+ "subsample participant total 4876956 in the secondary cohort "
213
+ "described in the methods section above and detailed in the "
214
+ "supplementary materials accompanying this paper."
215
+ )
216
+ out = _trim_caption_at_chart_data(cap)
217
+ # The tick run "2020 2021 2022 2023 2024 2025" appears earlier; trim
218
+ # there.
219
+ assert "2020 2021" not in out
220
+ assert "4876956" not in out
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes