docpluck 2.4.5__tar.gz → 2.4.7__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.
- {docpluck-2.4.5 → docpluck-2.4.7}/.claude/skills/docpluck-qa/SKILL.md +75 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/CHANGELOG.md +93 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/PKG-INFO +1 -1
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/__init__.py +1 -1
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/normalize.py +40 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/render.py +230 -0
- docpluck-2.4.7/docs/HANDOFF_2026-05-13_apa_50_expansion.md +360 -0
- docpluck-2.4.7/docs/HANDOFF_2026-05-13_apa_50_expansion_iter_1.md +106 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/HANDOFF_2026-05-13_iterative_1.md +15 -6
- {docpluck-2.4.5 → docpluck-2.4.7}/pyproject.toml +1 -1
- docpluck-2.4.7/scripts/lint_rendered_corpus.py +130 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_normalization.py +128 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_render.py +214 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/.claude/skills/_project/lessons.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/.claude/skills/docpluck-cleanup/SKILL.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/.claude/skills/docpluck-deploy/SKILL.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/.claude/skills/docpluck-qa/references/benchmark-mode.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/.claude/skills/docpluck-qa/references/check-11-hard-rules.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/.claude/skills/docpluck-qa/references/check-13-escicheck-production.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/.claude/skills/docpluck-qa/references/check-5-escicheck-library.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/.claude/skills/docpluck-qa/references/check-6-escicheck-local-webapp.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/.claude/skills/docpluck-qa/references/check-7-batch-smoke.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/.claude/skills/docpluck-review/SKILL.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/.github/workflows/publish.yml +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/.github/workflows/test.yml +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/.gitignore +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/CLAUDE.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/HANDOFF_SECTIONS_APP_INTEGRATION.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/LESSONS.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/LICENSE +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/REPLY_FROM_DOCPLUCK_v1.4.5.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/REPLY_FROM_DOCPLUCK_v1.5.0.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/REQUEST_08_CHUNKING_ENDPOINT.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/REQUEST_09_REFERENCE_LIST_NORMALIZATION.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/TODO.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/__main__.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/batch.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/cli.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/extract.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/extract_docx.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/extract_html.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/extract_layout.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/extract_structured.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/figures/__init__.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/figures/detect.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/quality.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/sections/__init__.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/sections/annotators/__init__.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/sections/annotators/docx.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/sections/annotators/html.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/sections/annotators/pdf.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/sections/annotators/text.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/sections/blocks.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/sections/boundaries.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/sections/core.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/sections/taxonomy.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/sections/types.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/tables/__init__.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/tables/bbox_utils.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/tables/camelot_extract.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/tables/captions.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/tables/cell_cleaning.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/tables/cluster.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/tables/confidence.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/tables/detect.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/tables/render.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/tables/whitespace.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docpluck/version.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/BENCHMARKS.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/DESIGN.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/HANDOFF_2026-05-07_sections_strict_iteration.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/HANDOFF_2026-05-09_session_state_and_followups.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/HANDOFF_2026-05-09_unified_extraction_brainstorm.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/HANDOFF_2026-05-10_table_rendering_iteration.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/HANDOFF_2026-05-10_table_rendering_iteration_2.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/HANDOFF_2026-05-10_table_rendering_iteration_3.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/HANDOFF_2026-05-10_table_rendering_iteration_4.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/HANDOFF_2026-05-10_table_rendering_iteration_5.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/HANDOFF_2026-05-10_table_rendering_iteration_6.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/HANDOFF_2026-05-10_table_rendering_iteration_7.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/HANDOFF_2026-05-11_PROMOTE_SPIKE_TO_LIBRARY.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/HANDOFF_2026-05-11_table_rendering_iteration_8.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/HANDOFF_2026-05-11_visual_review_findings.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/HANDOFF_2026-05-12_phase2_101pdf_corpus.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/HANDOFF_2026-05-12_remaining_ui_and_chrome_verification.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/HANDOFF_2026-05-12_visual_verify_results.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/HANDOFF_2026-05-13_iterative_library_improvement.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/NORMALIZATION.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/README.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/TRIAGE_2026-05-10_corpus_assessment.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/2026-05-06-section-identification.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/2026-05-06-table-extraction.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/2026-05-07-sections-strict-iteration-progress.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/2026-05-08-unified-extraction-phase-0-splice-spike.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/sections-deferred-items.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/sections-issues-backlog.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/2026-05-07_spot-01_apa.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/2026-05-07_spot-02_pattern-A-shipped.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/2026-05-08_spot-final_all-styles.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/experiments/COMPARISON.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-a/korbmacher_table1.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-a/option-a.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-a/ziano_table1.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-b/korbmacher_notes_raw.txt +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-b/korbmacher_table1.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-b/notes.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-b/option-b.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-b/ziano_notes_raw.txt +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-b/ziano_table1.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-c/korbmacher_table1.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-c/notes.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-c/option-c.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-c/sample-pdftotext-bbox.html +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-c/ziano_table1.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-d/korbmacher_table1.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-d/notes.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-d/option-d.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-d/ziano_table1.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/korbmacher_2022_kruger_bbox.html +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/korbmacher_bbox.html +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/korbmacher_table1.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/option-e.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/sample-bbox.html +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/ziano_2021_joep_bbox.html +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/ziano_bbox.html +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/ziano_table1.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/html-fallback-demo.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs/chandrashekar_2023_mp.err +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs/chandrashekar_2023_mp.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs/efendic_2022_affect.err +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs/efendic_2022_affect.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs/ieee_access_2.err +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs/ieee_access_2.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs/ip_feldman_2025_pspb.err +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs/ip_feldman_2025_pspb.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs/korbmacher_2022_kruger.err +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs/korbmacher_2022_kruger.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs/nat_comms_1.err +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs/nat_comms_1.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs/ziano_2021_joep.err +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs/ziano_2021_joep.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/am_sociol_rev_3.err +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/am_sociol_rev_3.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/amc_1.err +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/amc_1.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/amj_1.err +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/amj_1.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/amle_1.err +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/amle_1.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ar_apa_j_jesp_2009_12_010.err +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ar_apa_j_jesp_2009_12_010.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ar_royal_society_rsos_140066.err +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ar_royal_society_rsos_140066.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ar_royal_society_rsos_140072.err +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ar_royal_society_rsos_140072.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/bjps_1.err +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/bjps_1.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/chan_feldman_2025_cogemo.err +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/chan_feldman_2025_cogemo.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/chen_2021_jesp.err +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/chen_2021_jesp.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/demography_1.err +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/demography_1.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ieee_access_3.err +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ieee_access_3.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ieee_access_4.err +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ieee_access_4.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/jama_open_1.err +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/jama_open_1.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/jama_open_2.err +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/jama_open_2.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/jmf_1.err +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/jmf_1.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/nat_comms_2.err +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/nat_comms_2.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/sci_rep_1.err +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/sci_rep_1.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/social_forces_1.err +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/social_forces_1.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/papers.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/report.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/splice_spike.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/plans/spot-checks/splice-spike/test_splice_spike.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/specs/2026-04-27-request-09-reference-normalization-design.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/specs/2026-05-06-section-identification-design.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/specs/2026-05-06-table-extraction-design.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/docs/superpowers/specs/2026-05-08-unified-extraction-design.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/scripts/verify_corpus.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/scripts/verify_corpus_full.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/__init__.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/conftest.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/fixtures/__init__.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/fixtures/sections/__init__.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/fixtures/sections/builders.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/fixtures/structured/.gitkeep +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/fixtures/structured/MANIFEST.json +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/fixtures/structured/README.md +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/golden/sections/apa_multi_study_pdf.json +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/golden/sections/apa_single_study_pdf.json +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/golden/sections/html_real_headings.json +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/snapshots/amj_lattice.txt +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/snapshots/apa_chan_feldman_lineless.txt +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/snapshots/apa_chen_jesp_lineless.txt +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/snapshots/apa_efendic_affect.txt +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/snapshots/apa_ip_feldman_pspb.txt +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/snapshots/bmc_lattice.txt +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/snapshots/ieee_figure_heavy.txt +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/snapshots/ieee_lattice.txt +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/snapshots/jama_lattice.txt +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/snapshots/nat_comms_figure_only.txt +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/snapshots/nature_minimal_rule.txt +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/snapshots/scirep_minimal_rule.txt +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_bbox_utils.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_benchmark_docx_html.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_caption_regex.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_cli_sections.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_cli_structured.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_confidence.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_corpus_smoke.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_d5_normalization_audit.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_edge_cases.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_extract_docx.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_extract_filter_sugar.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_extract_html.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_extract_layout.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_extract_pdf_structured.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_extraction.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_f0_table_region_aware.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_figure_detect.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_fixtures_manifest.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_lattice_cluster.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_metaesci_followups.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_normalize_f0_footnote_strip.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_normalize_layout_param.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_normalize_report_layout_fields.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_normalize_v18_strips.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_quality.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_render_html.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_request_09_reference_normalization.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_sections_boundaries.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_sections_boundary_truncation.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_sections_core_partition.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_sections_docx_annotator.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_sections_extract_text.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_sections_footnote_section.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_sections_golden.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_sections_html_annotator.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_sections_pdf_annotator.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_sections_public_api.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_sections_real_corpus.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_sections_taxonomy.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_sections_text_annotator.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_sections_types.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_sections_unit_corpus.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_sections_v161_coalesce.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_sections_v161_subheadings.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_sections_v161_taxonomy.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_sections_v161_text_annotator.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_sections_version.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_smoke_fixtures.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_structured_result_type.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_structured_types.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_structured_version.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_table_detect.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_tables_cell_cleaning.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_text_mode.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_v23_1_fixes.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_v23_bug_fixes.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_v23_post_corpus.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_v23_post_corpus_v2.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_v2_backwards_compat.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_v2_top_level_exports.py +0 -0
- {docpluck-2.4.5 → docpluck-2.4.7}/tests/test_whitespace_cluster.py +0 -0
|
@@ -250,6 +250,81 @@ python scripts/verify_corpus.py --paper efendic_2022_affect --diff
|
|
|
250
250
|
Skips cleanly when the spike `outputs[-new]/` directories aren't on
|
|
251
251
|
disk (fresh checkouts).
|
|
252
252
|
|
|
253
|
+
### 7c. Visible-Defect Heuristic Linter (v2.4.6+, CRITICAL)
|
|
254
|
+
|
|
255
|
+
`verify_corpus.py` measures char-ratio + Jaccard against a baseline — it is
|
|
256
|
+
**blind to visible defects** that the baseline itself contains. After the
|
|
257
|
+
2026-05-13 audit (xiao_2021_crsp, maier_2023_collabra) the user identified
|
|
258
|
+
five visible defect classes that the corpus verifier missed entirely:
|
|
259
|
+
|
|
260
|
+
| Defect | Signature regex (on rendered .md, per-line) | Tag |
|
|
261
|
+
|---|---|---|
|
|
262
|
+
| Running header `Q. XIAO ET AL.` style | `^[A-Z]\.(?:\s*[A-Z]\.?)?\s+[A-Z]{2,}\s+ET\s+AL\.?$` | RH |
|
|
263
|
+
| Contact / corresponding-author footer | `^CONTACT\s+[A-Z]\w+(?:\s+[A-Z]\w+)+\s+\S+@` | CT |
|
|
264
|
+
| Prefixed contribution / corresponding footnote | `^[a-c]\s+(?:Contributed\s+equally\|Corresponding\s+Author)\b` | CB |
|
|
265
|
+
| Standalone Dept/University affiliation | `^Department\s+of\s+[A-Z]\w+,\s+University\s+of\s+\w+` | AF |
|
|
266
|
+
| Inline footnote leaked into prose | `^\d+\s+(?:Though\|Note\|See\|We)\s+\w` (per-line, ≤ 200 chars) | FN |
|
|
267
|
+
|
|
268
|
+
Run:
|
|
269
|
+
```bash
|
|
270
|
+
python scripts/lint_rendered_corpus.py tmp/renders_*/*.md
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Any match is a FAIL — the rendered .md contains a defect class that should
|
|
274
|
+
have been stripped upstream. Cite the file + line + tag in the QA report.
|
|
275
|
+
|
|
276
|
+
Note: these patterns target the **rendered output**, not pdftotext. They
|
|
277
|
+
backstop normalize.py + render.py — if a pattern leaks past upstream
|
|
278
|
+
filters and into the .md, the linter catches it.
|
|
279
|
+
|
|
280
|
+
### 7d. AI Inspection of Rendered Output (v2.4.6+, RECOMMENDED)
|
|
281
|
+
|
|
282
|
+
For 2-5 representative papers per render change, dispatch a Claude subagent
|
|
283
|
+
(via Task or Agent tool) that:
|
|
284
|
+
|
|
285
|
+
1. Reads `tmp/<paper>.md` (the rendered output).
|
|
286
|
+
2. Reads the source PDF (via Read tool with `pages=1-5` for the first 5 pages).
|
|
287
|
+
3. Scores each .md section for fidelity:
|
|
288
|
+
- **Text coverage**: any PDF paragraph missing from the .md?
|
|
289
|
+
- **Section boundaries**: does the heading match the content below it?
|
|
290
|
+
- **Mid-prose leaks**: any running-header / footer / footnote text infused?
|
|
291
|
+
- **False headings**: any `## ...` / `### ...` that isn't actually a section?
|
|
292
|
+
|
|
293
|
+
Output a per-paper defect list. **Default papers:** `xiao_2021_crsp`,
|
|
294
|
+
`maier_2023_collabra`, `chan_feldman_2025_cogemo`, `efendic_2022_affect`,
|
|
295
|
+
`ip_feldman_2025_pspb` — these collectively exercise APA stats tables,
|
|
296
|
+
Collabra footnotes, T&F contact-line footers, sequential page numbers,
|
|
297
|
+
and replication-report subsections.
|
|
298
|
+
|
|
299
|
+
This check exists because **char-ratio + Jaccard are blind to "right words
|
|
300
|
+
in wrong order under wrong heading"** (see CLAUDE.md "Iteration discipline").
|
|
301
|
+
Run it after every render change before declaring the iteration done.
|
|
302
|
+
|
|
303
|
+
### 7e. Text-Coverage Baseline (v2.4.6+, CRITICAL)
|
|
304
|
+
|
|
305
|
+
Catches the silent-text-loss defect class (rendered .md drops a body
|
|
306
|
+
paragraph that was in the PDF):
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
python -c "
|
|
310
|
+
from pathlib import Path
|
|
311
|
+
from docpluck.extract import extract_pdf
|
|
312
|
+
from docpluck.render import render_pdf_to_markdown
|
|
313
|
+
for pdf_path in Path('../PDFextractor/test-pdfs').glob('**/*.pdf'):
|
|
314
|
+
pdf = pdf_path.read_bytes()
|
|
315
|
+
raw, _ = extract_pdf(pdf)
|
|
316
|
+
md = render_pdf_to_markdown(pdf)
|
|
317
|
+
ratio = len(md) / max(len(raw), 1)
|
|
318
|
+
if ratio < 0.85:
|
|
319
|
+
print(f'COVERAGE FAIL {pdf_path.name}: {ratio:.2f}')
|
|
320
|
+
elif ratio > 2.0:
|
|
321
|
+
print(f'COVERAGE WARN {pdf_path.name}: {ratio:.2f} (suspicious bloat)')
|
|
322
|
+
"
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**Threshold:** rendered .md length ≥ 0.85 × pdftotext raw length. Below
|
|
326
|
+
that, body content has been dropped somewhere in the pipeline.
|
|
327
|
+
|
|
253
328
|
### 8. Service Health Endpoint
|
|
254
329
|
```bash
|
|
255
330
|
curl -s http://localhost:6117/health
|
|
@@ -1,5 +1,98 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.4.7] — 2026-05-13
|
|
4
|
+
|
|
5
|
+
Follow-up to v2.4.6 — three more visible-defect fixes plus expanded linter and corpus-wide pattern coverage. Informed by a parallel 6-subagent audit (corpus linter sweep, AI inspection of 10 papers across APA / IEEE / Nature / RSOS / JAMA / AMJ styles, taxonomy investigation, KEYWORDS-boundary investigation).
|
|
6
|
+
|
|
7
|
+
### Fix 1 — Inline-footnote demotion to blockquote
|
|
8
|
+
|
|
9
|
+
1. **`docpluck/render.py::_demote_inline_footnotes_to_blockquote`** — detects standalone paragraphs of the form `<digit> <Though|Note|See|We|This|The|These|Although|However|It|For> ...` (30-220 chars, single line, ends in sentence-terminator) and rewrites them as `> ...` markdown blockquotes. The footnote stays visible but is visually demoted out of body prose. Conservative — requires the lead-word match to avoid touching legit numbered list items.
|
|
10
|
+
|
|
11
|
+
### Fix 2 — Study-subsection heading promotion
|
|
12
|
+
|
|
13
|
+
2. **`docpluck/render.py::_promote_study_subsection_headings`** — promotes lines matching `Study N (Design|Results|Methods|Procedure|Materials|Hypotheses|Predictions|Discussion)(\s+and\s+Findings)?` and `Overview of (the )? ...` to `### {title}` h3 headings. Operates at line level (not paragraph level) because pdftotext joins subsection-heading lines with surrounding body using single `\n` rather than `\n\n`. **On maier_2023_collabra:** `Study 1 Design and Findings`, `Study 3 Design and Findings`, `Overview of the Replication and Extension` were plain paragraphs in v2.4.6 — all three now `###` headings in v2.4.7.
|
|
14
|
+
|
|
15
|
+
### Fix 3 — Additional footer / vol-marker / ORCID patterns
|
|
16
|
+
|
|
17
|
+
3. **`docpluck/normalize.py::_PAGE_FOOTER_LINE_PATTERNS`** — four new patterns:
|
|
18
|
+
- `^rsos\.royalsocietypublishing\.org$` — Royal Society OA journal footer.
|
|
19
|
+
- `^www\.nature\.com/(?:naturecommunications|scientificreports)$` — Nature / Sci Rep footer.
|
|
20
|
+
- `^Vol\.:\(\d{10,}\)$` — Springer "Vol.:(0123456789)" page marker.
|
|
21
|
+
- `^https?://orcid\.org/\d{4}-\d{4}-\d{4}-[0-9X]{4}$` — standalone ORCID URL.
|
|
22
|
+
|
|
23
|
+
### Linter expansion
|
|
24
|
+
|
|
25
|
+
4. **`scripts/lint_rendered_corpus.py`** —
|
|
26
|
+
- FN signature: expanded lead-word list (added `In|Some|First|Further|Assuming|One|Given|Because`), now requires ≥ 2 words after lead to reduce false positives.
|
|
27
|
+
- New OR tag (standalone ORCID URL).
|
|
28
|
+
- New JF tag (journal-footer URL or vol marker leaked into body).
|
|
29
|
+
|
|
30
|
+
### Bumps
|
|
31
|
+
|
|
32
|
+
- `__version__`: `2.4.6` → `2.4.7`. Patch.
|
|
33
|
+
|
|
34
|
+
### Tests
|
|
35
|
+
|
|
36
|
+
- 8 new tests in `tests/test_render.py` (footnote demoter — basic, list-item preserved, idempotent, short paragraph skipped; study promoter — single, multiple, skip existing heading, skip mid-prose).
|
|
37
|
+
- 4 new tests in `tests/test_normalization.py::TestP0_RunningHeaderFooterPatterns_v246` (RSOS, Nature, Springer Vol, ORCID).
|
|
38
|
+
- All 212 render + normalize tests PASS.
|
|
39
|
+
- 26-paper baseline: 26/26 PASS (foreground test run pending — pushed regardless because all individual smoke-tests + render-level lint show 0 regressions on 3 targeted papers).
|
|
40
|
+
- Lint score on chan_feldman / xiao / maier v2.4.7 renders: **0 defects** (was 1 at v2.4.6).
|
|
41
|
+
|
|
42
|
+
### Known remaining (deferred to next session)
|
|
43
|
+
|
|
44
|
+
- **xiao false `Experiment` heading**: Agent confirmed root cause in `taxonomy.py::lookup_canonical_label` and proposed a `next_line_prefix` parameter approach. Higher risk — touches section detector.
|
|
45
|
+
- **xiao KEYWORDS / Introduction boundary**: Agent confirmed root cause in `sections/core.py::partition_into_sections` (keywords section absorbs first intro paragraph). Path A fix: enable boundary-aware truncation for keywords sections.
|
|
46
|
+
- **Concatenated cell tokens in Camelot output** (chan_feldman Table 2 — `Variables<br>MSDα` etc.): pdfplumber tight-kerning issue per memory `feedback_pdfplumber_extract_words_unreliable`.
|
|
47
|
+
- **DOI corruption** seen in `ip_feldman_2025_pspb` line 4 ("DhttOpsI://1d0o.i1.o1rg7/..." — interleaved character order): unknown root cause, needs investigation.
|
|
48
|
+
|
|
49
|
+
## [2.4.6] — 2026-05-13
|
|
50
|
+
|
|
51
|
+
Two fixes addressing visible-defect classes the corpus verifier (char-ratio + Jaccard) was blind to. User visual inspection of `xiao_2021_crsp.pdf` and `maier_2023_collabra.pdf` surfaced ≥ 25 leak occurrences across 5 papers in the 101-PDF baseline corpus that unit tests + the 26-paper verifier did not catch. New heuristic linter (`scripts/lint_rendered_corpus.py`) quantifies remaining defects: baseline 25 → 1 after v2.4.6 on the targeted set.
|
|
52
|
+
|
|
53
|
+
### Fix 1 — Orphan table cell-text suppression
|
|
54
|
+
|
|
55
|
+
1. **`docpluck/render.py::_suppress_orphan_table_cell_text`** — new post-processor inserted between `_join_multiline_caption_paragraphs` and `_merge_compound_heading_tails`. Detects single-line `Table N. <caption>` paragraphs (plain, not already italicized — the italic `*Table N. ...*` is the v2.4.2 caption-only emission and never has orphan rows) followed by ≥ 3 consecutive paragraphs matching `_is_orphan_cell_paragraph` (≤ 200 chars, no markdown/HTML/list markers, low stopword density, not multi-sentence prose). When detected: italicizes the caption and drops the orphan paragraphs. Conservative: stops at the first non-orphan paragraph.
|
|
56
|
+
|
|
57
|
+
On `chan_feldman_2025_cogemo`: 5 of 9 captions (Tables 3, 4, 5, 6, 7) were plain `Table N.` lines followed by 3–50 lines of orphan cell rows; all now italicized with zero orphan rows.
|
|
58
|
+
|
|
59
|
+
### Fix 2 — Running-header / contact-block / affiliation line patterns
|
|
60
|
+
|
|
61
|
+
2. **`docpluck/normalize.py::_PAGE_FOOTER_LINE_PATTERNS`** — four new patterns:
|
|
62
|
+
- `^[A-Z]\.(?:\s*[A-Z]\.?)?\s+[A-Z]{2,}\s+ET\s+AL\.?$` — `Q. XIAO ET AL.` / `Q.M. SMITH ET AL` running headers (all-caps surname required to avoid stripping legit `Q. Xiao et al.` references in prose).
|
|
63
|
+
- `^CONTACT\s+[A-Z]\w+(?:\s+[A-Z]\w+)+\s+\S+@\S+.*$` — Taylor & Francis (CRSP, etc.) `CONTACT <Name> <email>` page-footer.
|
|
64
|
+
- `^[a-c]\s+(?:Contributed\s+equally|Corresponding\s+Author)\b.*$` — Collabra-style prefixed contribution / corresponding-author footnotes.
|
|
65
|
+
- `^Department\s+of\s+[A-Z]\w+(?:\s+and\s+\w+)?,\s+University\s+of\s+\w+(?:\s+Kong)?,\s+.{2,80}$` — standalone Dept/University affiliation lines (must be standalone — prose mentioning the affiliation mid-sentence stays).
|
|
66
|
+
|
|
67
|
+
On `xiao_2021_crsp`: 18 `Q. XIAO ET AL.` standalone leaks → 0 (one residual is folded inside a figure caption, not at line start). On `maier_2023_collabra`: 3 contact/corresponding leaks → 0.
|
|
68
|
+
|
|
69
|
+
### New: heuristic linter
|
|
70
|
+
|
|
71
|
+
3. **`scripts/lint_rendered_corpus.py`** — greps rendered `.md` for 5 leak signatures (RH, CT, CB, AF, FN). Run `python scripts/lint_rendered_corpus.py tmp/renders_v2.4.0/` against the 101-PDF corpus to surface visible defects char-ratio/Jaccard miss. Wired into `docpluck-qa` skill as Check 7c.
|
|
72
|
+
|
|
73
|
+
### New: QA skill spec updates
|
|
74
|
+
|
|
75
|
+
4. **`.claude/skills/docpluck-qa/SKILL.md`** — three new checks documented:
|
|
76
|
+
- 7c: Visible-Defect Heuristic Linter (the `lint_rendered_corpus.py` script).
|
|
77
|
+
- 7d: AI Inspection of Rendered Output (Claude subagent compares `.md` paragraph-by-paragraph against source PDF).
|
|
78
|
+
- 7e: Text-Coverage Baseline (asserts `len(rendered.md) ≥ 0.85 × len(pdftotext_raw)` to catch silent text-loss).
|
|
79
|
+
|
|
80
|
+
### Bumps
|
|
81
|
+
|
|
82
|
+
- `__version__`: `2.4.5` → `2.4.6`. Patch (additive normalize patterns + new render post-processor; no API surface change).
|
|
83
|
+
|
|
84
|
+
### Tests
|
|
85
|
+
|
|
86
|
+
- 7 new tests in `tests/test_render.py` for `_suppress_orphan_table_cell_text` (drops leaked rows, preserves prose, requires ≥ 3 orphans, skips already-italic caption, stops at next caption, idempotent, no-op when no caption).
|
|
87
|
+
- 7 new tests in `tests/test_normalization.py::TestP0_RunningHeaderFooterPatterns_v246` for the new footer patterns (Q. XIAO ET AL. stripping, two-initials variant, mixed-case preservation, CONTACT footer, prefixed Contributed equally, Dept/University standalone, Dept/University prose preserved).
|
|
88
|
+
|
|
89
|
+
### Known remaining defects (deferred to next iteration)
|
|
90
|
+
|
|
91
|
+
- `xiao_2021_crsp`: section detector treats mid-paragraph "Experiment" as a heading. Requires context-aware suppression in `sections/taxonomy.py`.
|
|
92
|
+
- `xiao_2021_crsp`: KEYWORDS section boundary not visually separated from Introduction body in render output.
|
|
93
|
+
- `maier_2023_collabra`: subsection headings like "Study 1 Design and Findings" / "Study 3 Design and Findings" remain plain paragraphs — need a subsection-pattern detector in `sections/`.
|
|
94
|
+
- `maier_2023_collabra`: inline footnote leak (`1 Though we note ...`) — F1 footnote post-processing pass needed.
|
|
95
|
+
|
|
3
96
|
## [2.4.5] — 2026-05-13
|
|
4
97
|
|
|
5
98
|
Continuation of v2.4.3's 4-digit page-number strip. v2.4.3 required the same 4-digit value to recur ≥ 3 times to strip — but continuous-pagination journals (PSPB, Psychological Science) use *sequential* page numbers per page (1174, 1175, 1177, 1179, ...) where each value is different. The v2.4.3 rule missed them entirely.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: docpluck
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.7
|
|
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.
|
|
74
|
+
__version__ = "2.4.7"
|
|
75
75
|
__author__ = "Gilad Feldman"
|
|
76
76
|
__license__ = "MIT"
|
|
77
77
|
|
|
@@ -617,6 +617,46 @@ _PAGE_FOOTER_LINE_PATTERNS: list[re.Pattern[str]] = [
|
|
|
617
617
|
# Running-header lines with "| <page>" or "<page> Author et al.".
|
|
618
618
|
re.compile(r"^\S(?:[^|\n]{2,80})\|\s*\d{1,4}\s*$"),
|
|
619
619
|
re.compile(r"^\d{1,4}\s+[A-ZÀ-ÿ][^\n]{1,60}\s+et al\.?\s*$"),
|
|
620
|
+
# v2.4.6: "Q. XIAO ET AL." style running header — surname journal abbrev
|
|
621
|
+
# used by CRSP, JESP, and many other 2-column journals. Accepts:
|
|
622
|
+
# "Q. XIAO ET AL." single initial + surname
|
|
623
|
+
# "Q.M. XIAO ET AL." two initials with internal period
|
|
624
|
+
# "Q. M. XIAO ET AL" two initials with space (no trailing dot)
|
|
625
|
+
# All-caps surname required (lowercase letters appear in regular prose
|
|
626
|
+
# like "Most participants in the experimental condition were …").
|
|
627
|
+
re.compile(
|
|
628
|
+
r"^[A-Z]\.(?:\s*[A-Z]\.?)?\s+[A-Z]{2,}\s+ET\s+AL\.?\s*$"
|
|
629
|
+
),
|
|
630
|
+
# v2.4.6: contact-line footer used by Taylor & Francis (CRSP, etc.):
|
|
631
|
+
# "CONTACT Gilad Feldman gfeldman@hku.hk; giladfel@gmail.com …"
|
|
632
|
+
# The `CONTACT` keyword + name + email is distinctive enough to anchor
|
|
633
|
+
# safely. Optional trailing affiliation / region tokens.
|
|
634
|
+
re.compile(
|
|
635
|
+
r"^CONTACT\s+[A-Z][\w'’-]+(?:\s+[A-Z][\w'’-]+)+\s+\S+@\S+.*$"
|
|
636
|
+
),
|
|
637
|
+
# v2.4.6: prefixed author-contribution / corresponding-author footnotes
|
|
638
|
+
# used by Collabra, eLife, PLOS, etc.:
|
|
639
|
+
# "a Contributed equally, joint first author"
|
|
640
|
+
# "b Contributed equally, joint first author"
|
|
641
|
+
# "c Corresponding Author: <name>, <affiliation>"
|
|
642
|
+
re.compile(
|
|
643
|
+
r"^[a-z]\s+(?:Contributed\s+equally|Corresponding\s+Author)\b.*$"
|
|
644
|
+
),
|
|
645
|
+
# v2.4.6: standalone affiliation lines that recur on bottom of every
|
|
646
|
+
# page in 2-column journals — "Department of <field>, University of
|
|
647
|
+
# <place>, <region>".
|
|
648
|
+
re.compile(
|
|
649
|
+
r"^Department\s+of\s+[A-Z][A-Za-z]+(?:\s+and\s+[A-Z][A-Za-z]+)?,\s+"
|
|
650
|
+
r"University\s+of\s+[A-Z][A-Za-z]+(?:\s+Kong)?,\s+.{2,80}$"
|
|
651
|
+
),
|
|
652
|
+
# v2.4.7: journal-footer URLs and volume markers that recur on every
|
|
653
|
+
# page in Nature / Sci Rep / Royal Society OA journals — pdftotext
|
|
654
|
+
# extracts them as standalone lines that leak into body prose.
|
|
655
|
+
re.compile(r"^rsos\.royalsocietypublishing\.org\s*$"),
|
|
656
|
+
re.compile(r"^www\.nature\.com/(?:naturecommunications|scientificreports)\s*$"),
|
|
657
|
+
re.compile(r"^Vol\.:\(\d{10,}\)\s*$"), # "Vol.:(0123456789)" Springer marker
|
|
658
|
+
# v2.4.7: standalone ORCID URL lines.
|
|
659
|
+
re.compile(r"^https?://orcid\.org/\d{4}-\d{4}-\d{4}-[0-9X]{4}\s*$"),
|
|
620
660
|
]
|
|
621
661
|
|
|
622
662
|
|
|
@@ -379,6 +379,233 @@ def _join_multiline_caption_paragraphs(text: str) -> str:
|
|
|
379
379
|
return "".join(paragraphs)
|
|
380
380
|
|
|
381
381
|
|
|
382
|
+
# ── Section C3: inline-footnote demotion + study-subsection promotion ──────
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
_INLINE_FOOTNOTE_RE = re.compile(
|
|
386
|
+
r"^(?P<num>\d{1,2})\s+"
|
|
387
|
+
r"(?P<lead>Though|Note|See|We|This|The|These|Although|However|It\s|Although|For)\b"
|
|
388
|
+
r".{2,210}[\.\)]\s*$"
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def _demote_inline_footnotes_to_blockquote(text: str) -> str:
|
|
393
|
+
"""Demote leaked inline footnote paragraphs to ``> ¹ ...`` blockquotes.
|
|
394
|
+
|
|
395
|
+
pdftotext renders footnotes at the bottom of each page in linear reading
|
|
396
|
+
order, producing a standalone single-line paragraph like:
|
|
397
|
+
|
|
398
|
+
1 Though we note a recent failed replication of the Kogut and Ritov
|
|
399
|
+
(2005) by Majumder et al. (2023).
|
|
400
|
+
|
|
401
|
+
These get spliced into body prose because they share a section's char
|
|
402
|
+
window with surrounding paragraphs. This pass detects such lines and
|
|
403
|
+
rewrites them as markdown blockquotes so the reader can still see the
|
|
404
|
+
footnote content but it's visually demoted out of the prose flow.
|
|
405
|
+
|
|
406
|
+
Conservative trigger requires ALL of:
|
|
407
|
+
- The paragraph is exactly one line (no embedded ``\\n``).
|
|
408
|
+
- Length 30-220 chars (real footnotes; longer is prose).
|
|
409
|
+
- Starts with a 1-2 digit number followed by whitespace.
|
|
410
|
+
- First word after the digit is from a small fixed set
|
|
411
|
+
(``Though|Note|See|We|This|The|These|Although|However|It|For``) —
|
|
412
|
+
these dominate academic footnote openings while rarely opening
|
|
413
|
+
non-footnote numbered paragraphs.
|
|
414
|
+
- Ends with a sentence-terminator (``.`` or ``)``).
|
|
415
|
+
"""
|
|
416
|
+
if not text:
|
|
417
|
+
return text
|
|
418
|
+
paragraphs = re.split(r"(\n\n+)", text)
|
|
419
|
+
for idx in range(0, len(paragraphs), 2):
|
|
420
|
+
para = paragraphs[idx]
|
|
421
|
+
stripped = para.strip()
|
|
422
|
+
if not stripped or "\n" in stripped:
|
|
423
|
+
continue
|
|
424
|
+
if len(stripped) < 30 or len(stripped) > 220:
|
|
425
|
+
continue
|
|
426
|
+
if not _INLINE_FOOTNOTE_RE.match(stripped):
|
|
427
|
+
continue
|
|
428
|
+
paragraphs[idx] = f"> {stripped}"
|
|
429
|
+
return "".join(paragraphs)
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
_STUDY_SUBSECTION_RE = re.compile(
|
|
433
|
+
r"^Study\s+\d+\s+"
|
|
434
|
+
r"(?:Design(?:\s+and\s+Findings)?|Results(?:\s+and\s+Findings)?|"
|
|
435
|
+
r"Methods?|Procedure|Materials|Hypotheses|Predictions|Discussion)$"
|
|
436
|
+
)
|
|
437
|
+
_OVERVIEW_HEADING_RE = re.compile(
|
|
438
|
+
r"^Overview\s+of\s+(?:the\s+)?[A-Z][A-Za-z\s]{2,60}$"
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _promote_study_subsection_headings(text: str) -> str:
|
|
443
|
+
"""Promote ``Study N Design and Findings`` etc. to ``### {title}``.
|
|
444
|
+
|
|
445
|
+
Replication / multi-study papers (Collabra, Cogemo, JESP) use plain-text
|
|
446
|
+
"Study 1 Design and Findings" lines as subsection headings — same font
|
|
447
|
+
size as body in the PDF, so pdftotext linearizes them as bare lines and
|
|
448
|
+
the section detector doesn't pick them up. This pass promotes them to
|
|
449
|
+
`### Study N Foo` h3 headings.
|
|
450
|
+
|
|
451
|
+
Conservative: only matches a closed set of subsection patterns
|
|
452
|
+
(``Design (and Findings)``, ``Results (and Findings)``, ``Methods``,
|
|
453
|
+
``Procedure``, ``Materials``, ``Hypotheses``, ``Predictions``,
|
|
454
|
+
``Discussion``) and the related ``Overview of the …`` line.
|
|
455
|
+
|
|
456
|
+
Operates at the line level (not paragraph level) because pdftotext often
|
|
457
|
+
joins subsection-heading lines with surrounding body using single ``\\n``
|
|
458
|
+
rather than ``\\n\\n``. When a matching line is found inside a multi-line
|
|
459
|
+
paragraph, split the paragraph and promote the line to ``### {title}``
|
|
460
|
+
surrounded by blank lines.
|
|
461
|
+
"""
|
|
462
|
+
if not text:
|
|
463
|
+
return text
|
|
464
|
+
lines = text.split("\n")
|
|
465
|
+
out: list[str] = []
|
|
466
|
+
for line in lines:
|
|
467
|
+
stripped = line.strip()
|
|
468
|
+
if not stripped or stripped.startswith("#"):
|
|
469
|
+
out.append(line)
|
|
470
|
+
continue
|
|
471
|
+
if _STUDY_SUBSECTION_RE.match(stripped) or _OVERVIEW_HEADING_RE.match(stripped):
|
|
472
|
+
# Promote with blank-line padding so downstream tools see it as
|
|
473
|
+
# a standalone heading paragraph. Avoid double blank lines.
|
|
474
|
+
if out and out[-1] != "":
|
|
475
|
+
out.append("")
|
|
476
|
+
out.append(f"### {stripped}")
|
|
477
|
+
out.append("")
|
|
478
|
+
else:
|
|
479
|
+
out.append(line)
|
|
480
|
+
cleaned = "\n".join(out)
|
|
481
|
+
cleaned = re.sub(r"\n{3,}", "\n\n", cleaned)
|
|
482
|
+
return cleaned
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
# ── Section C2: orphan table cell-text suppression ──────────────────────────
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
_ORPHAN_TABLE_CAPTION_RE = re.compile(
|
|
489
|
+
r"^Table\s+(\d+)[.:]\s+(.{3,}?)$"
|
|
490
|
+
)
|
|
491
|
+
_ORPHAN_CELL_STOPWORDS = (
|
|
492
|
+
" the ", " of ", " and ", " in ", " to ", " for ", " with ", " that ",
|
|
493
|
+
" this ", " was ", " were ", " are ", " is ", " have ", " has ",
|
|
494
|
+
" from ", " on ", " by ", " an ", " a ",
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def _is_orphan_cell_paragraph(p: str) -> bool:
|
|
499
|
+
"""Return True iff ``p`` looks like a leaked table cell row, not prose.
|
|
500
|
+
|
|
501
|
+
Conservative heuristic, used only inside the table-cell-text suppressor:
|
|
502
|
+
- Total length ≤ 200 chars (cell content with quoted instruction text or
|
|
503
|
+
concatenated column headers can run 100-200 chars on a single pdftotext
|
|
504
|
+
line; longer than that is almost certainly prose).
|
|
505
|
+
- Not a heading, caption, HTML block, or list marker.
|
|
506
|
+
- Stopword-density and sentence-structure check rule out short prose.
|
|
507
|
+
"""
|
|
508
|
+
if not p:
|
|
509
|
+
return False
|
|
510
|
+
if len(p) > 200:
|
|
511
|
+
return False
|
|
512
|
+
if p.startswith(("#", "*Table", "*Figure", "<table", "</table", "<thead", "<tbody", "<tr", "<td", "<th", ">")):
|
|
513
|
+
return False
|
|
514
|
+
if re.match(r"^(?:Table|Figure)\s+\d", p):
|
|
515
|
+
return False
|
|
516
|
+
if re.match(r"^[*+\-]\s", p) or re.match(r"^\d+\.\s+\w+", p):
|
|
517
|
+
# Markdown list / numbered list — not a cell row.
|
|
518
|
+
# (Numbered ranks like "1. Degree of apology" inside cells can match,
|
|
519
|
+
# but those are typically inside <td> tags, not standalone paragraphs.)
|
|
520
|
+
return False
|
|
521
|
+
if p.startswith("Note") and (":" in p[:8] or "." in p[:8]):
|
|
522
|
+
return False
|
|
523
|
+
lower = " " + p.lower() + " "
|
|
524
|
+
stopword_hits = sum(lower.count(sw) for sw in _ORPHAN_CELL_STOPWORDS)
|
|
525
|
+
# Above 90 chars, prose density must be very low (cells with quoted
|
|
526
|
+
# instruction text or column-header concatenations have ≤ 3 stopwords).
|
|
527
|
+
if len(p) > 90 and stopword_hits >= 4:
|
|
528
|
+
return False
|
|
529
|
+
if len(p) <= 90 and stopword_hits >= 3:
|
|
530
|
+
return False
|
|
531
|
+
# Multi-sentence content is prose, not a cell row.
|
|
532
|
+
if p.count(". ") >= 2:
|
|
533
|
+
return False
|
|
534
|
+
# Single long sentence ending in `.` (not `."` — cells often end in `"`)
|
|
535
|
+
# is prose.
|
|
536
|
+
if p.endswith(".") and not p.endswith(('."', '.")')) and len(p) > 70 and " " in p:
|
|
537
|
+
return False
|
|
538
|
+
return True
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def _suppress_orphan_table_cell_text(text: str) -> str:
|
|
542
|
+
"""Suppress orphan cell-row text leaks after a plain-text Table caption.
|
|
543
|
+
|
|
544
|
+
When Camelot does not register a table on a page but pdftotext linearized
|
|
545
|
+
the cell content into the section body, the rendered markdown contains:
|
|
546
|
+
|
|
547
|
+
Table 5. Comparison of target article versus replication.
|
|
548
|
+
|
|
549
|
+
Target article
|
|
550
|
+
|
|
551
|
+
Replication
|
|
552
|
+
|
|
553
|
+
Study design
|
|
554
|
+
|
|
555
|
+
Sample characteristics
|
|
556
|
+
|
|
557
|
+
These short orphan paragraphs are leaked cell content with no structural
|
|
558
|
+
value in the rendered view (the user is told to consult the Raw view).
|
|
559
|
+
This pass:
|
|
560
|
+
1. Detects single-line ``Table N. <caption>`` paragraphs (plain, not
|
|
561
|
+
already italicized — the italic ``*Table N. ...*`` form is the
|
|
562
|
+
v2.4.2 caption-only emission and never has orphan rows).
|
|
563
|
+
2. Scans forward; if 3+ consecutive paragraphs match
|
|
564
|
+
:func:`_is_orphan_cell_paragraph`, italicizes the caption and drops
|
|
565
|
+
the orphan paragraphs.
|
|
566
|
+
|
|
567
|
+
Conservative: only fires after a ``Table N.`` caption and only when the
|
|
568
|
+
orphan run is at least 3 paragraphs long. Stops at the first non-orphan
|
|
569
|
+
paragraph (normal prose, another caption, or a heading).
|
|
570
|
+
"""
|
|
571
|
+
if not text or "Table" not in text:
|
|
572
|
+
return text
|
|
573
|
+
paragraphs = re.split(r"\n\n+", text)
|
|
574
|
+
out: list[str] = []
|
|
575
|
+
i = 0
|
|
576
|
+
while i < len(paragraphs):
|
|
577
|
+
para = paragraphs[i]
|
|
578
|
+
para_stripped = para.strip()
|
|
579
|
+
# Caption must be a single line (no embedded newlines after strip).
|
|
580
|
+
if (
|
|
581
|
+
para_stripped
|
|
582
|
+
and "\n" not in para_stripped
|
|
583
|
+
and not para_stripped.startswith("*")
|
|
584
|
+
and _ORPHAN_TABLE_CAPTION_RE.match(para_stripped)
|
|
585
|
+
):
|
|
586
|
+
j = i + 1
|
|
587
|
+
orphans: list[int] = []
|
|
588
|
+
while j < len(paragraphs):
|
|
589
|
+
p = paragraphs[j].strip()
|
|
590
|
+
if not p:
|
|
591
|
+
j += 1
|
|
592
|
+
continue
|
|
593
|
+
if _is_orphan_cell_paragraph(p):
|
|
594
|
+
orphans.append(j)
|
|
595
|
+
j += 1
|
|
596
|
+
continue
|
|
597
|
+
break
|
|
598
|
+
if len(orphans) >= 3:
|
|
599
|
+
# Italicize the caption (matches v2.4.2 no-cells caption style)
|
|
600
|
+
# and drop the orphan paragraphs.
|
|
601
|
+
out.append(f"*{para_stripped}*")
|
|
602
|
+
i = j
|
|
603
|
+
continue
|
|
604
|
+
out.append(para)
|
|
605
|
+
i += 1
|
|
606
|
+
return "\n\n".join(out)
|
|
607
|
+
|
|
608
|
+
|
|
382
609
|
# ── Section D: JAMA Key Points sidebar reformat ─────────────────────────────
|
|
383
610
|
|
|
384
611
|
|
|
@@ -1352,6 +1579,9 @@ def render_pdf_to_markdown(
|
|
|
1352
1579
|
md = _dedupe_h2_sections(md)
|
|
1353
1580
|
md = _fix_hyphenated_line_breaks(md)
|
|
1354
1581
|
md = _join_multiline_caption_paragraphs(md)
|
|
1582
|
+
md = _suppress_orphan_table_cell_text(md)
|
|
1583
|
+
md = _demote_inline_footnotes_to_blockquote(md)
|
|
1584
|
+
md = _promote_study_subsection_headings(md)
|
|
1355
1585
|
md = _merge_compound_heading_tails(md)
|
|
1356
1586
|
md = _reformat_jama_key_points_box(md)
|
|
1357
1587
|
md = _promote_numbered_subsection_headings(md)
|