docpluck 2.4.95__tar.gz → 2.4.97__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.95 → docpluck-2.4.97}/.claude/skills/docpluck-deploy/SKILL.md +12 -11
- {docpluck-2.4.95 → docpluck-2.4.97}/.claude/skills/docpluck-iterate/LEARNINGS.md +52 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/.claude/skills/docpluck-qa/SKILL.md +15 -2
- {docpluck-2.4.95 → docpluck-2.4.97}/.claude/skills/docpluck-qa/references/check-11-hard-rules.md +13 -6
- {docpluck-2.4.95 → docpluck-2.4.97}/.claude/skills/docpluck-review/SKILL.md +7 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/CHANGELOG.md +26 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/CLAUDE.md +13 -3
- {docpluck-2.4.95 → docpluck-2.4.97}/PKG-INFO +1 -1
- {docpluck-2.4.95 → docpluck-2.4.97}/TODO.md +18 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/__init__.py +1 -1
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/extract_structured.py +105 -2
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/render.py +39 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/tables/cell_cleaning.py +20 -1
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/tables/flatten.py +118 -2
- docpluck-2.4.97/docs/TRIAGE_2026-06-21_head_v2.4.95_assessment.md +121 -0
- docpluck-2.4.97/docs/superpowers/handoffs/2026-06-21-rc-t-table-region-implementation.md +67 -0
- docpluck-2.4.97/docs/superpowers/handoffs/2026-06-22-dp2-dp5-flatten-fixes-commit.md +71 -0
- docpluck-2.4.97/docs/superpowers/specs/2026-06-21-rc-t-table-region-prose-contamination.md +85 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/pyproject.toml +1 -1
- docpluck-2.4.97/scripts/check_app_pin_sync.py +182 -0
- docpluck-2.4.97/tests/test_rc_t_degenerate_table_real_pdf.py +201 -0
- docpluck-2.4.97/tests/test_rc_t_layer2_raw_text_real_pdf.py +163 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_tables_flatten_blank_header_recovery.py +31 -1
- docpluck-2.4.97/tests/test_tables_superheader_alignment_real_pdf.py +168 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/.claude/skills/_project/canary.json +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/.claude/skills/_project/lessons.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/.claude/skills/docpluck-cleanup/SKILL.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/.claude/skills/docpluck-iterate/SKILL.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/.claude/skills/docpluck-iterate/references/ai-full-doc-verify.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/.claude/skills/docpluck-iterate/references/cycle-report-template.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/.claude/skills/docpluck-iterate/references/local-verification.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/.claude/skills/docpluck-iterate/references/rationalizations.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/.claude/skills/docpluck-iterate/references/real-library-real-pdf.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/.claude/skills/docpluck-iterate/references/release-flow.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/.claude/skills/docpluck-iterate/references/self-improvement.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/.claude/skills/docpluck-iterate/references/subagent-parallelization.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/.claude/skills/docpluck-iterate/references/three-tier-parity.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/.claude/skills/docpluck-qa/references/benchmark-mode.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/.claude/skills/docpluck-qa/references/check-13-escicheck-production.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/.claude/skills/docpluck-qa/references/check-5-escicheck-library.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/.claude/skills/docpluck-qa/references/check-6-escicheck-local-webapp.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/.claude/skills/docpluck-qa/references/check-7-batch-smoke.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/.github/workflows/bump-app-pin.yml +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/.github/workflows/publish.yml +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/.github/workflows/test.yml +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/.gitignore +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/CUSTOMER_UPDATE_2026-06-19_tables_sections_api.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/HANDOFF_SECTIONS_APP_INTEGRATION.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/LESSONS.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/LICENSE +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/README.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/REPLY_FROM_DOCPLUCK_v1.4.5.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/REPLY_FROM_DOCPLUCK_v1.5.0.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/REPLY_FROM_DOCPLUCK_v2.4.93.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/REPLY_FROM_DOCPLUCK_v2.4.94.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/REPLY_FROM_DOCPLUCK_v2.4.95.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/REQUEST_08_CHUNKING_ENDPOINT.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/REQUEST_09_REFERENCE_LIST_NORMALIZATION.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/REQUEST_10_TABLE_FLATTEN_HTTP_EXPOSURE.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/REQUEST_10_TIER2_ORPHANED_LABEL_ROW_RECOVERY.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/REQUEST_11_FLATTEN_FIELDS_NONCLINICAL_TABLES.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/__main__.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/batch.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/cli.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/extract.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/extract_columns.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/extract_docx.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/extract_html.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/extract_layout.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/figures/__init__.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/figures/detect.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/normalize.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/quality.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/sections/__init__.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/sections/annotators/__init__.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/sections/annotators/docx.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/sections/annotators/html.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/sections/annotators/pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/sections/annotators/text.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/sections/blocks.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/sections/boundaries.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/sections/core.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/sections/taxonomy.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/sections/types.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/tables/__init__.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/tables/bbox_utils.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/tables/camelot_extract.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/tables/captions.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/tables/cluster.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/tables/confidence.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/tables/detect.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/tables/render.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/tables/whitespace.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/telemetry.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docpluck/version.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/BENCHMARKS.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/BENCHMARKS_liteparse_2026-06.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/DESIGN.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-07_sections_strict_iteration.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-09_session_state_and_followups.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-09_unified_extraction_brainstorm.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-10_table_rendering_iteration.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-10_table_rendering_iteration_2.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-10_table_rendering_iteration_3.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-10_table_rendering_iteration_4.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-10_table_rendering_iteration_5.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-10_table_rendering_iteration_6.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-10_table_rendering_iteration_7.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-11_PROMOTE_SPIKE_TO_LIBRARY.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-11_table_rendering_iteration_8.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-11_visual_review_findings.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-12_phase2_101pdf_corpus.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-12_remaining_ui_and_chrome_verification.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-12_visual_verify_results.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-13_apa_50_expansion.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-13_apa_50_expansion_iter_1.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-13_apa_50_expansion_iter_2.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-13_iterate_skill_first_use.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-13_iterative_1.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-13_iterative_library_improvement.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-13_table_extraction_next_iteration.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-14_continue_iterations_v2_4_30_to_15n.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-14_full_corpus_iteration_v2_4_30.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-14_iterate_6_cycles_complete.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-14_iterate_9_cycle_run.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-14_iterate_resume_4_cycles.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-14_iterate_v2_4_31_cycle_15n.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-14_phase_5d_gold_audit_v2_4_29.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-15_autonomous_apa_first_10h.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-15_iterate_apa_run_1.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-16_ai-gold-instructions.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-16_iterate_apa_run_2.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-16_iterate_apa_run_3.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-16_iterate_run_4_final.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-16_iterate_run_4_fix_and_continue.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-16_iterate_run_5.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-16_iterate_run_6.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-17_iterate_run_7.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-17_iterate_run_8.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-17_iterate_run_9.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-18_iterate_run_9_cont.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-18_iterate_run_9_cont2.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-20_iterate_run_9_cont3.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-22_iterate_run_9_session4_final.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-22_iterate_run_9_session5_close.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-25_haiku-orchestration-pretest.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-05-25_pretest-followups.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-06-08_iterate_splice-wordintegrity-runningheader.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-06-08_untested_sweep_v2.4.81.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-06-13_sciencearena_grobid_liteparse.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-06-15_docpluck-iterate-resume.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-06-15_rc1-step2-continue.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-06-16_docpluck-iterate-resume.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-06-17_iterate_resume-cycle1.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-06-17_iterate_v2491_shipped.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-06-18_iterate_v2492_affiliation_caption-revert.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/HANDOFF_2026-06-20_request11_flatten_nonclinical_tables.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/ITERATION_VERIFICATION_LESSONS.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/LIBRARY_APP_SYNC.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/NORMALIZATION.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/README.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/TRIAGE_2026-05-10_corpus_assessment.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/TRIAGE_2026-05-14_phase_5d_gold_audit.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/TRIAGE_2026-06-08_untested_corpus_sweep.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/TRIAGE_2026-06-15_head_v2.4.88_assessment.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/handoffs/2026-05-22-b1-next-iteration.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/handoffs/2026-05-22-b2-remaining-halluc-head.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/handoffs/2026-05-22-b3-b7-structural-defects.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/handoffs/2026-05-22-residual-after-locally-doable-pass.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/handoffs/2026-05-23-bundled-residual-cycle-CLOSED.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/handoffs/2026-05-23-residual-after-iterate-spine-cycles-1-3.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/handoffs/2026-05-25-canary-audit-architecture-and-cluster-A-B-C-landed.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/handoffs/2026-05-25-wrapup-r4-cycle.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/handoffs/2026-05-26-run-11-cluster-A-ter-and-C-bis-landed.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/handoffs/2026-05-26-text-extraction-defects-from-citationguard-audit.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/handoffs/2026-06-07-text-extraction-defects-from-citationguard-iterate.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/handoffs/2026-06-07-v2.4.78-landed-canary-iterate.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/handoffs/2026-06-07-v2.4.79-findings-1-2-cleared.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/handoffs/2026-06-20_docpluck-skill-file-edits-from-app-cron-fix.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/2026-05-06-section-identification.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/2026-05-06-table-extraction.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/2026-05-07-sections-strict-iteration-progress.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/2026-05-08-unified-extraction-phase-0-splice-spike.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/2026-05-23-haiku-orchestration-pretest.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/sections-deferred-items.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/sections-issues-backlog.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/2026-05-07_spot-01_apa.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/2026-05-07_spot-02_pattern-A-shipped.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/2026-05-08_spot-final_all-styles.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/experiments/COMPARISON.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-a/korbmacher_table1.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-a/option-a.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-a/ziano_table1.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-b/korbmacher_notes_raw.txt +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-b/korbmacher_table1.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-b/notes.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-b/option-b.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-b/ziano_notes_raw.txt +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-b/ziano_table1.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-c/korbmacher_table1.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-c/notes.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-c/option-c.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-c/sample-pdftotext-bbox.html +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-c/ziano_table1.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-d/korbmacher_table1.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-d/notes.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-d/option-d.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-d/ziano_table1.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/korbmacher_2022_kruger_bbox.html +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/korbmacher_bbox.html +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/korbmacher_table1.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/option-e.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/sample-bbox.html +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/ziano_2021_joep_bbox.html +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/ziano_bbox.html +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/experiments/option-e/ziano_table1.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/html-fallback-demo.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs/chandrashekar_2023_mp.err +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs/chandrashekar_2023_mp.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs/efendic_2022_affect.err +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs/efendic_2022_affect.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs/ieee_access_2.err +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs/ieee_access_2.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs/ip_feldman_2025_pspb.err +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs/ip_feldman_2025_pspb.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs/korbmacher_2022_kruger.err +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs/korbmacher_2022_kruger.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs/nat_comms_1.err +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs/nat_comms_1.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs/ziano_2021_joep.err +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs/ziano_2021_joep.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/am_sociol_rev_3.err +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/am_sociol_rev_3.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/amc_1.err +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/amc_1.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/amj_1.err +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/amj_1.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/amle_1.err +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/amle_1.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ar_apa_j_jesp_2009_12_010.err +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ar_apa_j_jesp_2009_12_010.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ar_royal_society_rsos_140066.err +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ar_royal_society_rsos_140066.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ar_royal_society_rsos_140072.err +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ar_royal_society_rsos_140072.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/bjps_1.err +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/bjps_1.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/chan_feldman_2025_cogemo.err +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/chan_feldman_2025_cogemo.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/chen_2021_jesp.err +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/chen_2021_jesp.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/demography_1.err +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/demography_1.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ieee_access_3.err +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ieee_access_3.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ieee_access_4.err +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/ieee_access_4.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/jama_open_1.err +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/jama_open_1.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/jama_open_2.err +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/jama_open_2.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/jmf_1.err +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/jmf_1.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/nat_comms_2.err +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/nat_comms_2.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/sci_rep_1.err +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/sci_rep_1.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/social_forces_1.err +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/outputs-new/social_forces_1.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/papers.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/report.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/splice_spike.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/plans/spot-checks/splice-spike/test_splice_spike.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/specs/2026-04-27-request-09-reference-normalization-design.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/specs/2026-05-06-section-identification-design.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/specs/2026-05-06-table-extraction-design.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/specs/2026-05-08-unified-extraction-design.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/specs/2026-05-23-haiku-orchestration-pretest-design.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/specs/2026-06-07-ip_feldman-B4-R4-column-interleave-diagnosis.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/docs/superpowers/specs/2026-06-08-rc1-region-aware-column-architecture.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/scripts/__init__.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/scripts/check_docs_consistency.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/scripts/harness/README.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/scripts/harness/VERIFIER_PROMPT.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/scripts/harness/__init__.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/scripts/harness/baseline_matrix.json +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/scripts/harness/checks.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/scripts/harness/corpus.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/scripts/harness/corpus_manifest.json +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/scripts/harness/extract.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/scripts/harness/gold_keys.json +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/scripts/harness/inspect.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/scripts/lint_rendered_corpus.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/scripts/pretest_capture_tokens.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/scripts/verify_corpus.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/scripts/verify_corpus_full.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/__init__.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/conftest.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/fixtures/__init__.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/fixtures/sections/__init__.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/fixtures/sections/builders.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/fixtures/structured/.gitkeep +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/fixtures/structured/MANIFEST.json +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/fixtures/structured/README.md +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/golden/sections/apa_multi_study_pdf.json +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/golden/sections/apa_single_study_pdf.json +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/golden/sections/html_real_headings.json +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/snapshots/amj_lattice.txt +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/snapshots/apa_chan_feldman_lineless.txt +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/snapshots/apa_chen_jesp_lineless.txt +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/snapshots/apa_efendic_affect.txt +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/snapshots/apa_ip_feldman_pspb.txt +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/snapshots/bmc_lattice.txt +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/snapshots/ieee_figure_heavy.txt +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/snapshots/ieee_lattice.txt +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/snapshots/jama_lattice.txt +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/snapshots/nat_comms_figure_only.txt +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/snapshots/nature_minimal_rule.txt +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/snapshots/scirep_minimal_rule.txt +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_a3c_leading_zero_decimal_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_a4_ci_period_to_comma.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_affiliation_heading_promote_guard_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_all_caps_section_promote_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_bbox_utils.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_benchmark_docx_html.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_cambridge_footer_strip_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_camelot_lattice_augment.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_camelot_temp_cleanup.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_canary_provenance.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_caption_only_table_heading_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_caption_regex.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_chart_data_trim_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_cid_minus_recovery_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_cli_sections.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_cli_structured.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_confidence.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_corpus_smoke.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_d5_normalization_audit.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_dropped_minus_layout_recovery_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_edge_cases.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_elsevier_footer_strip_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_equation_page_header_strip_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_extract_columns.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_extract_docx.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_extract_filter_sugar.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_extract_html.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_extract_layout.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_extract_pdf_structured.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_extraction.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_f0_table_region_aware.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_fffd_comparison_recovery_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_figure_caption_trim_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_figure_detect.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_fixtures_manifest.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_hallucinated_heading_continuation_guard.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_harness_text_loss_reflow.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_harvard_refs_pagebreak_stitch.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_jama_open_cluster_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_lattice_cluster.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_letterspaced_label_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_ligature_decomposition_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_lt_operator_recovery_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_mathitalic_greek_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_metaesci_followups.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_minus_sign_recovery_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_normalization.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_normalize_a3_r2_body_integer_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_normalize_f0_footnote_strip.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_normalize_idempotent_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_normalize_layout_param.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_normalize_metadata_leak_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_normalize_report_layout_fields.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_normalize_soft_hyphen_dehyphenation.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_normalize_v18_strips.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_numbered_heading_promotion_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_numbered_section_promotion_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_o5_reference_inversion_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_orphan_multilevel_number_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_orphan_section_number_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_p0r_recurring_running_header_strip.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_preserve_math_glyphs_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_pretest_capture_tokens.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_pua_glyph_recovery_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_quality.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_r1_whitespace_cells_wiring_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_r4_column_correction_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_rc1_banded_column_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_rc1_general_column_correction_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_render.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_render_frontmatter_masthead.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_render_html.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_render_subsection_chain_promotion.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_request_09_reference_normalization.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_residual_2026_05_23_bundled.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_roman_numeral_section_promote_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_section_row_label_no_merge_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_sections_boundaries.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_sections_boundary_truncation.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_sections_core_partition.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_sections_docx_annotator.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_sections_extract_text.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_sections_footnote_section.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_sections_golden.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_sections_html_annotator.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_sections_pdf_annotator.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_sections_public_api.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_sections_real_corpus.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_sections_taxonomy.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_sections_text_annotator.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_sections_types.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_sections_unit_corpus.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_sections_v161_coalesce.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_sections_v161_subheadings.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_sections_v161_taxonomy.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_sections_v161_text_annotator.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_sections_version.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_single_column_subsection_promote_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_smoke_fixtures.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_structured_result_type.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_structured_types.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_structured_version.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_table_caption_cell_region_real_pdf.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_table_detect.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_tables_cell_cleaning.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_tables_flatten.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_text_mode.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_v23_1_fixes.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_v23_bug_fixes.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_v23_post_corpus.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_v23_post_corpus_v2.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_v2_backwards_compat.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_v2_top_level_exports.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tests/test_whitespace_cluster.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tools/canary_provenance.py +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tools/fix_python_env.ps1 +0 -0
- {docpluck-2.4.95 → docpluck-2.4.97}/tools/render_for_audit.py +0 -0
|
@@ -84,28 +84,29 @@ print(f'All imports OK; docpluck=={info[\"version\"]} normalize={info[\"normaliz
|
|
|
84
84
|
"
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
-
### 4. Cross-Repo Library Version Sync (CRITICAL)
|
|
87
|
+
### 4. Cross-Repo Library Version Sync (CRITICAL — "when we bump the package, we bump the app")
|
|
88
88
|
|
|
89
|
-
Verify the app's `service/requirements.txt` git pin matches the library's latest tag.
|
|
89
|
+
Verify the app's `service/requirements.txt` git pin matches the library's latest released tag. A mismatch means the deploy silently ships the OLD library to prod. **The pin is read from docpluckapp `origin/master` (what Railway deploys), NOT the local clone — a stale local checkout shows an old pin even when prod is correctly synced, which almost causes a phantom "fix".** The shared gate (also run by `/docpluck-qa` check 11b and `/docpluck-review` rule 22) is the single source of truth:
|
|
90
90
|
|
|
91
91
|
```bash
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
echo "
|
|
95
|
-
echo "
|
|
96
|
-
if [ "$LIB_VERSION" != "$APP_PIN" ]; then
|
|
97
|
-
echo "❌ MISMATCH — bump PDFextractor/service/requirements.txt to docpluck @ git+https://github.com/giladfeldman/docpluck.git@v$LIB_VERSION before deploying"
|
|
92
|
+
cd C:/Users/filin/Dropbox/Vibe/MetaScienceTools/docpluck && python scripts/check_app_pin_sync.py || {
|
|
93
|
+
echo "Cross-repo pin sync FAILED — recover before deploying:"
|
|
94
|
+
echo " - re-push the tag: git push origin v<VERSION> (re-fires bump-app-pin.yml), OR"
|
|
95
|
+
echo " - hand-bump PDFextractor/service/requirements.txt to @v<VERSION> and push to docpluckapp master."
|
|
98
96
|
exit 1
|
|
99
|
-
|
|
97
|
+
}
|
|
98
|
+
# Note: a working-tree __version__ ahead of the latest tag is reported UNRELEASED (not a failure) —
|
|
99
|
+
# that is the normal pre-flight state; the "Library Release Step" below tags+pushes it, which
|
|
100
|
+
# fires the auto-bump, and post-deploy check 3 confirms Railway /health reports the new version.
|
|
100
101
|
|
|
101
102
|
# Also verify the API.md examples are not stale beyond a major version
|
|
103
|
+
LIB_VERSION=$(grep '^__version__' C:/Users/filin/Dropbox/Vibe/MetaScienceTools/docpluck/docpluck/__init__.py | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
|
|
102
104
|
API_DOC_VERSION=$(grep -oE 'docpluck_version["\s:]+[0-9]+\.[0-9]+\.[0-9]+' C:/Users/filin/Dropbox/Vibe/MetaScienceTools/PDFextractor/API.md | head -1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
|
|
103
105
|
LIB_MAJOR_MINOR=$(echo "$LIB_VERSION" | cut -d. -f1,2)
|
|
104
106
|
DOC_MAJOR_MINOR=$(echo "$API_DOC_VERSION" | cut -d. -f1,2)
|
|
105
107
|
if [ "$LIB_MAJOR_MINOR" != "$DOC_MAJOR_MINOR" ]; then
|
|
106
|
-
echo "
|
|
108
|
+
echo "WARN: API.md examples reference docpluck_version $API_DOC_VERSION; library is at $LIB_VERSION. Update PDFextractor/API.md."
|
|
107
109
|
fi
|
|
108
|
-
echo "✅ Library version sync OK"
|
|
109
110
|
```
|
|
110
111
|
|
|
111
112
|
### 5. Verify Vercel Environment Variables
|
|
@@ -1275,3 +1275,55 @@ aren't skipped.
|
|
|
1275
1275
|
2. **A bounded sample gives FALSE CONFIDENCE — the full-corpus regression gate (rule 19) is non-negotiable and earned its keep here.** The 11-paper diff said "only the target changed, ship it"; the 48-paper diff revealed 4 real-heading false positives. I nearly shipped a regression off a green bounded sample. ALWAYS run the guard-live-vs-bypassed diff over the WHOLE corpus before trusting a heading-promotion change — bounded samples miss the long tail where the FP pattern lives.
|
|
1276
1276
|
|
|
1277
1277
|
**Open queue (run stays OPEN — standing verdict FAIL):** the cell-label cases are table-content-as-prose → fold into the table cluster, NOT a standalone render guard; the TABLE cluster (highest impact, architectural bbox decision outstanding since 2026-05-22 — needs user scope decision); RC-1 band path (multi-session, riskiest); residual metadata-leaks. The clean render-layer slice that DID ship this run (affiliation v2.4.92) plus the prior single-column v2.4.91 are done; the remainder is architectural.
|
|
1278
|
+
|
|
1279
|
+
---
|
|
1280
|
+
|
|
1281
|
+
## 2026-06-21 · Resume · cycle 3 · full real AI-verify @ v2.4.95 = 7/7 canary FAIL; corpus at an ARCHITECTURAL boundary (3 root causes, all need sign-off); 2 open_findings adjudicated NOT-defects
|
|
1282
|
+
|
|
1283
|
+
**Target:** "keep addressing todo, iterating and improving." Found the run half-open after the v2.4.95 Request-11 ship (cycles 1-2), TRIAGE stale @ v2.4.88, 2 carried-over open_findings. **Verdict: cycle 3 = VERIFY + ADJUDICATE + SURFACE-DECISION, NO code shipped.** Standing FAIL.
|
|
1284
|
+
|
|
1285
|
+
**The canary-audit clobber masked a fully-broken corpus AGAIN (memory `feedback_canary_audit_clobbers_phase5d`, re-confirmed live).** The fresh HEAD canary render (`canary-2dbdd98`) carried 5 `verdict:PASS` files — but every one was `raw_verdicts:[AUDIT_DEFERRED_TO_AGENT,AUDIT_DEFERRED_TO_AGENT] → union PASS`. `AUDIT_DEFERRED` means the headless Sonnet deferred to the in-session agent and the hook recorded the *non-verdict* as PASS. Re-running 7 real in-session Sonnet verifiers vs the article-finder golds → **7/7 FAIL.** **Never trust a canary-audit PASS whose `raw_verdicts` are `AUDIT_DEFERRED`; it is a placeholder, not a verification.** Re-verify manually after a commit before trusting I3-green.
|
|
1286
|
+
|
|
1287
|
+
**Narrow-scope verification hides broad defects (Phase-0.8 cross-output lesson, re-proven).** maier_2023_collabra was recorded PASS in cycles 1-2 — but those only checked the *specific* Request-11 flatten fields (T8/T10). The full-document verify this cycle shows maier broadly FAIL: T5 unstructured fallback, T7 garbled with body text, T8/T9/T11 empty headers, section displacement. The narrow check wasn't wrong for its scope; it never looked at the rest of the doc. **A per-feature "PASS" is not a per-document PASS — when the canary rotation comes around, verify the WHOLE document, not the fields the last cycle touched.**
|
|
1288
|
+
|
|
1289
|
+
**All 7 papers' defects cluster to 3 ARCHITECTURAL root causes (TRIAGE_2026-06-21):**
|
|
1290
|
+
1. **RC-T table-bbox** (widest — all 7, single + two-column): Camelot grabs furniture/adjacent prose → empty shells, garbled cells, missing headers, duplicate dumps, orphan `### Table N`. Proof: ip_feldman Table-10 cells = running-header `Ip and Feldman` + page `15` + `Discussion` heading + Discussion prose + 1 real row, all bbox `(0,0,0,0)` → render *correctly* drops the `<table>`. Bbox decision open since 2026-05-22.
|
|
1291
|
+
2. **RC-1 column/sidebar interleave** (chan_feldman, chandrashekar, plos_med sidebar): the furniture-strip is *defeated by* the interleave (plos_med's whole front-matter sidebar lands before the Abstract; `## Abstract Published: <date>` weld). Spec ready (2026-06-08 region-aware).
|
|
1292
|
+
3. **RC-B7 deleted-minus glyph** (ar_apa): 5 body-prose betas sign-flipped `β=−.022`→`b=.022`. 3-path decision pending.
|
|
1293
|
+
|
|
1294
|
+
**No clean non-architectural win remains — chased the 3 best leads, each bottomed out in a root cause above** (plos_med masthead-strip = interleave; ip_feldman Table-10 splice = bbox-garbage; abstract-date weld = interleave). Per the skill (avoid C4 without sign-off) + must-stop ("fix needs an architectural decision"), surfaced the 3-way decision to the user rather than diving in blind.
|
|
1295
|
+
|
|
1296
|
+
**2 open_findings adjudicated NOT docpluck defects (LEAVE NOTHING BEHIND — inspected locally, did not defer):**
|
|
1297
|
+
- `collabra_77859` "Table 3" vs gold "Table 2": source text-channel caption (line 866) is verbatim `Table 3. Study 4: Dish sets`; docpluck correct, **gold mis-numbered** → article-finder.
|
|
1298
|
+
- `collabra_90203` Table 10 r=.59 vs .63: pdftotext literally emits `.59` (text-line 1706), Camelot agrees; `.63` is visual-only → source text-layer/visual divergence, OCR-only. Documented limitation.
|
|
1299
|
+
|
|
1300
|
+
**Addendum (same session, user authorized "do all three, 1-3"): RC-B7 was ALREADY DONE; RC-T root-caused; checkpointed before the big multi-session table work.**
|
|
1301
|
+
|
|
1302
|
+
**RC-B7 (authorized #1) = already implemented — verify the codebase before re-solving an "architectural" item.** I set out to build the B7 layout-channel minus recovery the old TRIAGE called for — and found it already exists as **W0h** (`normalize.recover_dropped_minus_via_layout`), wired (render.py:5079→sections→normalize.py:3170) and regression-tested (`tests/test_dropped_minus_layout_recovery_real_pdf.py`). HEAD renders `b=-.022 / -.88 / -.428` correctly (4/5 ar_apa betas). My own cycle-3 FAIL was a **verifier over-flag** — it quoted the W0h-recovered `-.022`/`-.428` and still called them GLYPH defects. **Lesson: before treating a TRIAGE "architectural, needs sign-off" item as open, grep the library for an existing implementation — a prior session may have already shipped it. The verifier's FAIL is a hypothesis, not a fact; reproduce + read the code at HEAD first.** Residuals (`.245` pixel-minus, β→b) confirmed OCR-tier: probed BOTH channels — pdfplumber also extracts `b` (font AdvPSMP10) and shows no `.245` minus glyph; outside docpluck's MIT text+layout architecture, already documented in the W0h comment.
|
|
1303
|
+
|
|
1304
|
+
**RC-T (authorized #2) root cause = the FULL-PAGE-BBOX signature.** ip_feldman Table 10's region bbox is `(53, 53, 577, 800)` — top→bottom spans the whole of page 15 (vs Tables 1-9 = tight sub-region bands), so the "table" swallowed the running header `Ip and Feldman` + page `15` + `Discussion` heading + Discussion prose + 1 real data row (cells all bbox `(0,0,0,0)`). **The fix must key on CELL CONTENT (furniture/prose signatures), NOT bbox-size** — legitimate landscape Tables 6/7/8 also have tall bboxes; a degenerate region → clean unstructured fallback (no orphan `### Table N`, no prose-as-cells). This is a multi-session, high-regression-surface change.
|
|
1305
|
+
|
|
1306
|
+
**Pacing decision (per LEARNINGS rule: full-corpus gate is non-negotiable; don't rush heading/table changes at session-tail).** After a long verification+investigation session, I deliberately did NOT start the RC-T/RC-1 implementation — a table-bbox change rushed without a careful full-corpus regression pass is exactly how the cycle-3 caption-follows revert happened. Checkpointed with the characterization done so RC-T can be a focused dedicated effort. Standing verdict stays FAIL (RC-T + RC-1 open).
|
|
1307
|
+
|
|
1308
|
+
---
|
|
1309
|
+
|
|
1310
|
+
## Run: 2026-06-21 (PM) · cycle 1 · RC-T Option A implemented → v2.4.96
|
|
1311
|
+
|
|
1312
|
+
### Outcome
|
|
1313
|
+
- **SHIPPED (incremental):** RC-T degenerate prose-table strip — `render.py::_strip_phantom_camelot_tables`. Net corpus impact: **exactly 2 tables** now fail-clean (maier_2023 Table 7, chan_feldman Table 6), all else byte-identical. ip_feldman Table 10 (the RC-T canonical "orphan") was **already** fail-clean at HEAD; the real fixable garbage was elsewhere. Standing verdict remains **PARTIAL/FAIL** — RC-T Layer-1 recovery + RC-1 interleave are deferred (user-scoped to Option A), so canaries still FAIL on those classes.
|
|
1314
|
+
|
|
1315
|
+
### Blind Spots (corrections to the prior session's RC-T characterization)
|
|
1316
|
+
- **The prior entry framed RC-T as "full-page-bbox → emit a cell-content degenerate guard / route to unstructured fallback / no orphan `### Table N`."** Reproduction at HEAD corrected this: ip_feldman T10 is **already stripped** at HEAD (its folded-prose `<th>` already trips `_strip_phantom_camelot_tables` via the fn≥3 path), rendering `### Table 10` + caption + no table = already fail-clean. The actual open gap was much smaller: **"the" — the single most common English function word — was missing from `_FUNCTION_WORDS_IN_PROSE`**, so a body-prose `<th>` with `fn==2 (+the)` slipped under the bar (maier T7 "Following the analyses conducted in Study 1 of Small": fn=in,of=2; verb=following,conducted=2). Lesson: a "build a whole new guard" item can collapse to a one-word set fix once you reproduce + read the EXISTING guard at HEAD (compare RC-B7's "already implemented as W0h").
|
|
1317
|
+
- **Cell-content "majority prose/furniture" is NOT a safe degenerate discriminator** — my first attempt (a data-layer `_table_cells_are_degenerate` keyed on majority prose) FP'd immediately on chan_feldman Table 5, a **legitimate comparison table whose cells are full descriptive sentences by design** (prose-in-cells is normal for comparison/design/instrument tables). Reverted. The reliable signature is narrower: a **≥8-word sentence-shaped `<th>`** (real column headers are short noun phrases; a sentence in the header == Camelot folded body prose). Legit prose-bearing tables keep short headers.
|
|
1318
|
+
|
|
1319
|
+
### Edge Cases (the title-leak FP — caught only by the full-corpus scan)
|
|
1320
|
+
- **A real table can leak its own TITLE into the `<th>`** (Camelot whole-page bbox grabs the caption row as the header). `aom/amp_1` Table 5's `<th>` is its caption "Improving Scholarly Impact … Practice" over a REAL grid (Domains/Policymaking/Practice + data). Adding "the" naively strips this real table = regression. **Discriminator: caption-token overlap.** A title-leak `<th>` shares ≥60% of its tokens with a caption line; a body-prose `<th>` (maier T7, chan_feldman T6) shares ~none. Gate the new strip on `not is_title_leak`.
|
|
1321
|
+
- **A relaxation can secretly be a broadening.** My first title-leak exclusion skipped title-leak `<th>`s for ALL fn-paths — which would have changed the verdict for **37 tables already stripped at HEAD** (many with title-shaped `<th>`s like jama-open-1 T3 "Effect of Time-Restricted Eating…", bmc-pub-health-2 "Table 2 Collection of equations…"). Whether each of those 37 is correct-strip vs wrongly-stripped-real-table is a **pre-existing, unverified question**. Fix: **scope the new behavior to the marginal case only** — fire only when "the" crosses fn 2→3 (`fn_count<3 and fn_count+the_count>=3`), so every HEAD-stripped table is byte-identical and only genuinely-new body-prose strips are added.
|
|
1322
|
+
|
|
1323
|
+
### Improvements (verification methodology)
|
|
1324
|
+
- **th-level full-corpus FP scan beats a bounded render sample AND a full render-diff for a surgical guard change.** `tmp/repro/fp_scan.py` extracted all 152 corpus PDFs once and, per table, computed the exact `_strip_phantom` th counts with/without "the" — pinpointing the precise change set (3 fn-flip tables; 37 HEAD-stripped candidates for un-strip regression) in ~13 min, far cheaper than 304 renders. For a change confined to one code path, analyze THAT path corpus-wide rather than diffing whole renders. (Still confirmed the deciding cases with real renders.)
|
|
1325
|
+
- **The full-corpus gate earned its keep AGAIN.** The 4-paper sample (ip_feldman/maier/chan_feldman/plos_med) looked clean at every step; the FP (amp_1 T5) and the 37-table over-broad-exclusion lived ONLY in the long tail — exactly the cycle-3-caption-follows trap. Never trust a bounded sample for a table/heading guard.
|
|
1326
|
+
|
|
1327
|
+
### Verification Gaps / Deferred (queued, not dropped)
|
|
1328
|
+
- **RC-T Layer-1 recovery** — actually RECOVERING lost table data via tight `table_areas` (plos_med T5's 13 SAE rows; chan_feldman T2 column-squish) — out of Option-A scope, deferred by the user.
|
|
1329
|
+
- **Audit of the ~37 `_strip_phantom` th-stripped tables** — some title-shaped `<th>` strips may be wrongly-stripped REAL tables (pre-existing, predates this cycle). Needs its own verification cycle (render each, judge real-vs-phantom). Surfaced explicitly; NOT silently shipped around.
|
|
@@ -25,7 +25,7 @@ If QA surfaces an issue — any issue, however small, whether pre-existing, alre
|
|
|
25
25
|
## Project Context
|
|
26
26
|
|
|
27
27
|
- **App repo (private):** `C:\Users\filin\Dropbox\Vibe\MetaScienceTools\PDFextractor` (GitHub: giladfeldman/docpluckapp)
|
|
28
|
-
- **Library repo (public):** `C:\Users\filin\Dropbox\Vibe\docpluck` (GitHub: giladfeldman/docpluck, PyPI: docpluck)
|
|
28
|
+
- **Library repo (public):** `C:\Users\filin\Dropbox\Vibe\MetaScienceTools\docpluck` (GitHub: giladfeldman/docpluck, PyPI: docpluck)
|
|
29
29
|
- **Frontend:** Next.js 16 + Auth.js + Drizzle (in `frontend/`), port 6116
|
|
30
30
|
- **Service:** Python FastAPI importing `docpluck` library (in `service/`), port 6117
|
|
31
31
|
- **Database:** Neon Postgres (docpluck project)
|
|
@@ -607,6 +607,18 @@ print('F0 sentinel (preserved): PASS')
|
|
|
607
607
|
"
|
|
608
608
|
```
|
|
609
609
|
|
|
610
|
+
### 11b. Cross-Repo Library ↔ App Version Sync (CRITICAL — "when we bump the package, we bump the app")
|
|
611
|
+
|
|
612
|
+
Asserts the app's docpluck git pin equals the library's latest released tag, so production never silently runs an old library. Reads the pin from docpluckapp **origin/master** (production-authoritative — a stale local clone shows an old pin even when prod is synced), so it fetches first.
|
|
613
|
+
|
|
614
|
+
```bash
|
|
615
|
+
cd C:\Users\filin\Dropbox\Vibe\MetaScienceTools\docpluck && python scripts/check_app_pin_sync.py
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
**Gate:** exit 0 (`PASS: in sync ...`). Exit 1 = the app pin lags the latest library tag — the `bump-app-pin.yml` auto-bump missed; recover NOW (re-push the tag to re-fire the workflow, or hand-bump `PDFextractor/service/requirements.txt` to `@v<latest>` and push to docpluckapp `master`), then re-run until PASS — never report the run clean while it lags. Exit 2 = could not reach docpluckapp `origin/master` (treat as FAIL, not PASS; offline dev only may pass `--allow-local-fallback`, which reads the local clone and prints a stale-clone warning). A working-tree `__version__` ahead of the latest tag is reported as UNRELEASED (not a failure) — tag + push it so the app auto-bumps.
|
|
619
|
+
|
|
620
|
+
See CLAUDE.md "Two-Repo Architecture → Library ↔ app version sync".
|
|
621
|
+
|
|
610
622
|
### 12. Production Deployment (Vercel + Railway)
|
|
611
623
|
```bash
|
|
612
624
|
# Vercel frontend
|
|
@@ -658,10 +670,11 @@ Opt-in cross-format benchmark suite --- DOCX corpus integrity, DOCX↔PDF parity
|
|
|
658
670
|
| 9 | Database connectivity | PASS/FAIL | 7/7 tables |
|
|
659
671
|
| 10 | Admin API | PASS/FAIL | health + stats |
|
|
660
672
|
| 11 | Hard rules (4+2 checks) | PASS/FAIL | no -layout, no AGPL, U+2212, version, new modules, F0 sentinel |
|
|
673
|
+
| 11b | Cross-repo lib↔app version sync | PASS/FAIL | app pin (origin/master) == latest library tag |
|
|
661
674
|
| 12 | Production health | PASS/FAIL | HTTP codes |
|
|
662
675
|
| 13 | ESCIcheck 10-PDF (production) | PASS/FAIL/SKIP | X/10 passed |
|
|
663
676
|
|
|
664
|
-
**Overall: X/
|
|
677
|
+
**Overall: X/16 checks passed**
|
|
665
678
|
|
|
666
679
|
### Issues Found
|
|
667
680
|
- [list any failures with exact error messages and file:line]
|
{docpluck-2.4.95 → docpluck-2.4.97}/.claude/skills/docpluck-qa/references/check-11-hard-rules.md
RENAMED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
_Extracted from [../SKILL.md](../SKILL.md). Full procedure lives here._
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
cd C:\Users\filin\Dropbox\Vibe\PDFextractor\service && python -c "
|
|
6
|
+
cd C:\Users\filin\Dropbox\Vibe\MetaScienceTools\PDFextractor\service && python -c "
|
|
7
7
|
import re
|
|
8
8
|
|
|
9
9
|
# Rule 1: No -layout flag in pdftotext calls (check library)
|
|
@@ -30,15 +30,22 @@ print('Rule 2 (no AGPL): PASS')
|
|
|
30
30
|
import docpluck.normalize as _nm_mod
|
|
31
31
|
with open(_nm_mod.__file__, 'rb') as _f:
|
|
32
32
|
_norm_bytes = _f.read()
|
|
33
|
-
assert b'
|
|
33
|
+
assert b'\xe2\x88\x92' in _norm_bytes, 'U+2212 normalization missing' # U+2212 as UTF-8 bytes
|
|
34
34
|
print('Rule 3 (U+2212 norm): PASS')
|
|
35
35
|
|
|
36
|
-
# Rule 4: Library version is consistent
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
# Rule 4: Library version is internally consistent (__init__.py == pyproject.toml).
|
|
37
|
+
# (Do NOT freeze a literal version here — it rots. This checks the two in-repo
|
|
38
|
+
# sources agree; the cross-repo app-pin sync is qa check 11b, see note below.)
|
|
39
|
+
import docpluck, re, pathlib
|
|
40
|
+
init_ver = docpluck.__version__
|
|
41
|
+
pyproject = (pathlib.Path(docpluck.__file__).resolve().parent.parent / 'pyproject.toml').read_text(encoding='utf-8')
|
|
42
|
+
proj_ver = re.search(r'(?m)^version\s*=\s*.(\d+\.\d+\.\d+)', pyproject).group(1)
|
|
43
|
+
assert init_ver == proj_ver, f'Version mismatch: __init__={init_ver} pyproject={proj_ver}'
|
|
44
|
+
print(f'Rule 4 (version consistency, __init__==pyproject=={init_ver}): PASS')
|
|
40
45
|
"
|
|
41
46
|
```
|
|
42
47
|
|
|
48
|
+
> **Cross-repo pin sync** (the app's `@v<VERSION>` pin == the library's latest released tag) is a *separate* gate — qa check **11b** and the review hard rule, both via `python scripts/check_app_pin_sync.py` (reads docpluckapp `origin/master`). Rule 4 above only checks the library's *internal* version consistency (`__init__.py` == `pyproject.toml`).
|
|
49
|
+
|
|
43
50
|
---
|
|
44
51
|
|
|
@@ -213,6 +213,12 @@ The product principle is "one nightly server-resource-bounded cron per concern."
|
|
|
213
213
|
- **Check:** parse `frontend/vercel.json`; assert exactly two cron entries, namely `/api/admin/blob-cleanup` at `0 3 * * *` AND `/api/cron/daily-digest` at `0 9 * * *`.
|
|
214
214
|
- **Severity:** BLOCKER on any third cron — REQUEST CHANGES with the question "why isn't daily-digest enough?"
|
|
215
215
|
|
|
216
|
+
### 22. Library ↔ app version pin sync (cross-repo, 2026-06-20 — "when we bump the package, we bump the app")
|
|
217
|
+
The app imports the library via a git pin in `PDFextractor/service/requirements.txt` (`docpluck[all] @ git+...@v<VERSION>`). This pin — on docpluckapp **origin/master**, the production-authoritative source Railway deploys — MUST always equal the library's latest released `v*` tag. A lagging pin silently runs the OLD library in production. The `bump-app-pin.yml` workflow auto-bumps it on tag push, but it is best-effort and has missed silently, so review VERIFIES rather than assumes.
|
|
218
|
+
- **Check:** run `python scripts/check_app_pin_sync.py` (from the docpluck repo). Exit 0 = synced. It fetches docpluckapp `origin/master` and compares the pin to the latest library tag — a stale local clone is NOT trusted (it shows an old pin even when prod is correctly synced).
|
|
219
|
+
- **Check:** whenever the diff bumps `docpluck/__init__.py:__version__` / `pyproject.toml:version`, confirm a matching `v<VERSION>` tag will be / has been pushed so the auto-bump can fire — an untagged version bump leaves the app pinned behind. The script reports this as UNRELEASED.
|
|
220
|
+
- **Severity:** BLOCKER if the app pin lags the latest released library tag (production runs the old library). WARN if a working-tree version bump is not yet tagged (release step pending).
|
|
221
|
+
|
|
216
222
|
## Review Checklist
|
|
217
223
|
|
|
218
224
|
### Python Service (`service/`)
|
|
@@ -266,6 +272,7 @@ The product principle is "one nightly server-resource-bounded cron per concern."
|
|
|
266
272
|
- [ ] Changes reflected in CLAUDE.md if architectural
|
|
267
273
|
- [ ] LESSONS.md updated if a new pitfall was discovered
|
|
268
274
|
- [ ] TODO.md updated if features were added/completed
|
|
275
|
+
- [ ] App pin in sync with library (hard rule 22): `python scripts/check_app_pin_sync.py` exits 0 (app pin on docpluckapp `origin/master` == latest library tag). BLOCKER if it lags.
|
|
269
276
|
|
|
270
277
|
## Output Format
|
|
271
278
|
|
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.4.97] — 2026-06-22
|
|
4
|
+
|
|
5
|
+
**Three table fixes shipped together (combined from two concurrent sessions): type the skipped p+df columns (DP-2), stop dropping / mis-binding two-header-row tables (DP-5), and stop the table raw_text fallback swallowing body prose (RC-T Layer-2).** `TABLE_EXTRACTION_VERSION` → `2.4.2`; no `NORMALIZATION_VERSION` / `SECTIONING_VERSION` change. DP-2/DP-5 are render-visible in the inline flattened-table blocks + the `.tables.jsonl` sidecar `fields` (the `<table>` HTML gains the previously-dropped data rows); RC-T Layer-2 is render-visible in the `unstructured-table` fallback blocks. DP-2/DP-5 filed in `ESCIcheckapp/docs/DOCPLUCK_HANDOFF_2026-06-21.md`; RC-T Layer-2 per `docs/superpowers/specs/2026-06-21-rc-t-table-region-prose-contamination.md`.
|
|
6
|
+
|
|
7
|
+
- **DP-2 — type the unlabeled p and df columns.** `tables.flatten._recover_blank_roles` recovered the leading test statistic and the `d [CI]` column of a header-stripped result table but left the bare p-value and df columns between them untyped, so `collabra.77859` Table 3 emitted `fields: {group, t, d, CI}` and dropped the `p` (`.551`) and `df` (`260.54`). A new Pass 4.5 types a still-blank column that is a bare `.XXX` with no comparison op as `p`, and a bare integer / Welch-decimal sitting between the test statistic and its `est/CI` column as `df` — keyed on data shape + position relative to the already-recovered roles, never bare position. The four Table-3 rows now carry `p` and `df`.
|
|
8
|
+
|
|
9
|
+
- **DP-5 — two-row-header parallel-arm tables: recover the first data row and align centered super-headers.** `collabra.90203` Table 10 delivered only 5 of its 6 correlation rows (the Identifiable/Explicit-learning row was silently dropped), and the Original/Replication arms of `xiao_2021` Table 4 were swapped. Three coupled root-cause fixes: (a) `cell_cleaning._is_header_like_row` now counts APA value shapes (leading-dot decimal, bracketed CI, operator-prefixed p, `N/A`) as data via `_DATA_VALUE_CELL_RE`, so a real first data row is no longer mis-read as a third header row (the bracket branch requires a digit and no letters inside, so a genuine `[95% CI]` header stays a header); (b) `tables.flatten._detect_column_groups` re-derives arm boundaries from equal-width blocks of the data region — each must contain exactly one super-label — so a *centered* super-label (camelot stream loses colspan and folds it mid-span) no longer swaps arm values or pushes a stat column into the label region; left-aligned super-headers stay byte-identical; (c) `tables.flatten._classify_column` reads a folded super-header cell's role from its sub-part so a folded `…<sep>95% CI` column is still typed `CI`. Table 10 now emits all 6 conditions split into Target-article / Replication arms with correct `r` / `n` / `CI` / `p`; xiao Table 4 arms are no longer swapped; incidentally recovers `chan_feldman` Table 8 arm labels and `jama_open_2` Table 3 HR estimates + CIs.
|
|
10
|
+
|
|
11
|
+
- **RC-T Layer-2 — stop the table raw_text fallback swallowing body prose.** When Camelot recovers no cells, `extract_structured._extract_table_body_text` linearises the text after a caption as the `unstructured-table` fallback; its per-line prose gate (`_line_is_body_prose`, len ≥ 80) misses prose that pdftotext WRAPPED into short (~48-char) lines, so a short table's caption-anchored region overshot the table end and swallowed Results/Discussion prose. Two FP-safe structural fixes: **(a) Note-anchor** — a table's `Note:` footnote is, by convention, its last element, so trim everything after the note paragraph (`chan_feldman` T1/T3 + `efendic_2022` T5 trailing Discussion prose removed; the stat rows + the note are kept); **(b) degenerate-prose guard** — suppress a fallback block that STARTS mid-sentence with a lowercase multi-letter word AND is majority sentence-shaped prose, so the renderer emits a clean caption-only table (`chan_feldman` T9 was an entire verbatim duplicate of `## Discussion` — now caption-only, no duplication). FP-safe by construction: real table cells start with a header / label / number / single-letter item marker, never a wrapped mid-sentence continuation — hypotheses ("a There is a positive association…"), descriptive rows ("Median age"), and instrument fragments are preserved. Keyed on the structural overshoot signature, never paper identity.
|
|
12
|
+
|
|
13
|
+
Verification: new real-PDF + contract regression tests (`tests/test_tables_superheader_alignment_real_pdf.py`) — collabra.90203 T10 six-conditions/correct-arms + xiao T4 not-swapped (each FAILS at HEAD, PASSES after), plus `_is_header_like_row` / `_detect_column_groups` contract cases; `tests/test_tables_flatten_blank_header_recovery.py` extended for DP-2. A full-corpus (101-PDF) cached-table flatten diff confirms no clean-table regression — every changed table is a recovered row, a correct arm split, a recovered field, or a removed stat-less spurious row; already-garbage tables shuffle without a clean table regressing. Broad pytest green (real-PDF Camelot tests run serially per file — non-deterministic under cumulative load). RC-T Layer-2 adds `tests/test_rc_t_layer2_raw_text_real_pdf.py` (6 contract + 4 real-PDF: chan T1 Note-anchor, T9 suppress-no-duplication, T3 preserved) and an independent full-corpus 101-PDF guard-live-vs-bypassed raw_text diff (`grew=0 changed=0`; 4 trims + 8 prose-suppressions only). A 7-canary Sonnet AI-gold verify confirms every table this release touched is correct (chan T1/T3/T9, maier T10 six-conditions, xiao T4 arms) with no new TEXT-LOSS / HALLUCINATION.
|
|
14
|
+
|
|
15
|
+
**Deferred (pre-existing, user decision 2026-06-22):** the remaining canary AI-verify FAILs are the architectural backlog, NOT regressions from this release — RC-T **Layer-1** table-data recovery (`table_areas`; e.g. plos_med Table 5's SAE rows, chan_feldman / chandrashekar under-extraction) and RC-1 two-column / sidebar column-interleave. Tracked in `docs/TRIAGE_2026-06-21_head_v2.4.95_assessment.md`; intentionally not addressed here.
|
|
16
|
+
|
|
17
|
+
## [2.4.96] — 2026-06-21
|
|
18
|
+
|
|
19
|
+
**RC-T (Option A): strip Camelot "tables" that are absorbed body prose, not data.** Render-only — `render.py::_strip_phantom_camelot_tables`; no `TABLE_EXTRACTION_VERSION` / `NORMALIZATION_VERSION` / `SECTIONING_VERSION` change.
|
|
20
|
+
|
|
21
|
+
Camelot runs free-form (`flavor="stream"`, no `table_areas`), so on a text-heavy page it returns a whole-page bbox and folds body prose into the `<thead>`. `_strip_phantom_camelot_tables` already drops such a table when a `<th>` is sentence-shaped prose (≥8 words, ≥3 function words, ≥2 verb-shape words) — but its function-word set was **missing `"the"`**, the single most common English function word, so a body-prose `<th>` with `fn=2 + "the"` slipped under the `fn≥3` bar and a garbage prose `<table>` survived. Two corpus cases: `10.1525/collabra.90203` (maier) Table 7 (`<th>` "Following the analyses conducted in Study 1 of Small") and `10.1080/02699931.2024.2434156` (chan_feldman) Table 6 ("associations between the six measures of interest: …").
|
|
22
|
+
|
|
23
|
+
The fix counts `"the"`, but is **scoped to the marginal case** (a `<th>` that crosses fn 2→3 only because of "the") so every table already stripped at HEAD via the `fn≥3` path stays byte-identical, and is **gated on NOT being a title-leak**: `aom/amp_1` Table 5 leaks its own caption ("Improving Scholarly Impact … Practice") into the `<th>` over a REAL grid (Domains / Policymaking / Practice + data) — a caption-token-overlap test (≥60%) keeps such title-leak tables. **Net corpus impact: exactly 2 tables stripped, all others byte-identical** (verified by a full-corpus th-level scan over all 152 corpus PDFs + render diffs). Fail-clean: the `### Table N` heading + caption remain (table_parity preserved); the stripped prose is a Camelot duplicate, so the clean original survives in the body (no TEXT-LOSS). ip_feldman Table 10 — the RC-T canonical "orphan" — was already fail-clean at HEAD and is unchanged.
|
|
24
|
+
|
|
25
|
+
Verification: 8 new real-PDF regression tests (`tests/test_rc_t_degenerate_table_real_pdf.py`): maier T7 + chan_feldman T6 stripped (each FAILS at HEAD, PASSES after), amp_1 T5 + chan_feldman T2/T5 (real tables) preserved, maier prose survives in body (no TEXT-LOSS), ip_feldman T10 prose stays out of `<table>`. Broad pytest green; 7-canary AI-verify shows the touched tables fail-clean with no new TEXT-LOSS / HALLUCINATION.
|
|
26
|
+
|
|
27
|
+
**Deferred (separate cycles, surfaced not dropped):** (1) RC-T **Layer-1 recovery** — actually RECOVERING lost table data via tight `table_areas` (plos_med Table 5's 13 SAE rows; chan_feldman Table 2 column-squish) — out of Option-A scope. (2) **Audit of the ~37 corpus tables** stripped by the existing `_strip_phantom_camelot_tables` th-prose / section-token paths — some title-shaped `<th>` strips may be wrongly-stripped REAL tables (pre-existing; predates this change), needs its own verify-each-render cycle. (3) RC-1 two-column / sidebar interleave.
|
|
28
|
+
|
|
3
29
|
## [2.4.95] — 2026-06-20
|
|
4
30
|
|
|
5
31
|
**Flatten now populates `fields` for non-clinical result tables (REQUEST_11).** `TABLE_EXTRACTION_VERSION` → `2.4.0`; no `NORMALIZATION_VERSION` / `SECTIONING_VERSION` change. v2.4.94 solved the clinical PROSECCO table (labelled headers); this closes the two reproducers whose `fields` still came back `{}` — header-stripped result tables and tables packing parallel arms into single cells.
|
|
@@ -50,16 +50,25 @@ docpluck[all] @ git+https://github.com/giladfeldman/docpluck.git@v<VERSION>
|
|
|
50
50
|
|
|
51
51
|
When this library releases a new version, the app's `requirements.txt` git pin must be bumped or production silently keeps running the old library. The `/docpluck-deploy` skill's pre-flight check 4 enforces this.
|
|
52
52
|
|
|
53
|
+
### Library ↔ app version sync (HARD RULE — when we bump the package, we bump the app; verify, don't assume)
|
|
54
|
+
|
|
55
|
+
**Invariant: the app's docpluck pin and the library version are ALWAYS in sync.** The `@v<VERSION>` pin in `PDFextractor/service/requirements.txt` on **docpluckapp `origin/master`** (what Railway deploys) MUST equal the library's latest released `v*` tag. A lagging pin = production silently runs the old library. Bumping the package therefore *is* bumping the app — the two are never released independently.
|
|
56
|
+
|
|
57
|
+
- **Mechanism (best-effort, automated):** `.github/workflows/bump-app-pin.yml` fires on every `v*.*.*` tag push to this repo and auto-commits the pin bump to docpluckapp `master`, which triggers the Railway redeploy. This usually "just works" — but it is **best-effort**: an Actions outage, an expired `APP_REPO_TOKEN`, or a regex drift can let it miss *silently* (it has happened). **Never assume the bump landed.**
|
|
58
|
+
- **Verification (mandatory, deterministic):** run `python scripts/check_app_pin_sync.py` — it reads the pin from docpluckapp `origin/master` (production-authoritative, NOT your local clone) and compares it to the latest library tag. Exit 0 = synced. This gate is wired into `/docpluck-qa`, `/docpluck-review`, and `/docpluck-deploy` (pre-flight check 4); every release MUST pass it.
|
|
59
|
+
- **A stale LOCAL clone lies.** A local `PDFextractor` checkout that hasn't fetched shows an *old* pin even when production is correctly synced — and almost causes a phantom "fix". ALWAYS verify against `origin/master` (the script does this for you); never judge sync from a local working-tree file.
|
|
60
|
+
- **Recovery when it drifted:** re-push the tag (`git push origin v<VERSION>` re-fires the workflow), or hand-bump `service/requirements.txt` to `@v<VERSION>` and push to docpluckapp `master` (triggers Railway redeploy). Then re-run the gate and confirm Railway `/health` reports `docpluck_version == <VERSION>`.
|
|
61
|
+
|
|
53
62
|
## Release flow (library → production)
|
|
54
63
|
|
|
55
64
|
1. Make + commit changes in this repo. Bump `__version__` (in `docpluck/__init__.py`), `version` (in `pyproject.toml`), and `NORMALIZATION_VERSION` (in `docpluck/normalize.py`) consistently.
|
|
56
65
|
2. Update `CHANGELOG.md`.
|
|
57
66
|
3. Push to `main`, then tag: `git tag v<VERSION> && git push --tags`.
|
|
58
67
|
4. (Optional) Publish to PyPI: `python -m build && twine upload dist/*`.
|
|
59
|
-
5.
|
|
60
|
-
6. Run `/docpluck-deploy` from the docpluck repo — pre-flight check 4
|
|
68
|
+
5. The tag push auto-fires `bump-app-pin.yml`, which bumps the `@v<VERSION>` pin in `PDFextractor/service/requirements.txt` on docpluckapp `master` and triggers the Railway redeploy. **This is best-effort — verify it landed:** run `python scripts/check_app_pin_sync.py` (exit 0 = synced against `origin/master`). If it drifted, recover per "Library ↔ app version sync" above. Also update any frozen version examples in `PDFextractor/API.md`.
|
|
69
|
+
6. Run `/docpluck-deploy` from the docpluck repo — pre-flight check 4 runs the same sync gate and the post-deploy step confirms Railway `/health` reports the new version.
|
|
61
70
|
|
|
62
|
-
|
|
71
|
+
The most common failure mode is assuming the auto-bump landed when it silently missed — step 5's `check_app_pin_sync.py` gate catches it. The deploy skill, qa, and review all run it.
|
|
63
72
|
|
|
64
73
|
## Spike work queue (table-rendering iteration)
|
|
65
74
|
|
|
@@ -79,6 +88,7 @@ Skipping step 5 is the most common failure mode. The deploy skill catches it.
|
|
|
79
88
|
|
|
80
89
|
- **NEVER call the Anthropic API. ALL Claude model calls go through Claude Max via Claude Code.** Allowed: `Agent` tool in-session (with `model="sonnet"` for the audit subagent); headless `claude -p --model sonnet` from `.git/hooks/*` and `tools/canary_audit.sh`; `mcp__scheduled-tasks__create_scheduled_task` invoking Claude Code. Forbidden: `import anthropic`, `ANTHROPIC_API_KEY` anywhere in this repo or any related repo (`docpluckapp`, `escicheck`, `2Rmarkdown`, `CitationGuard`), `.github/workflows/*` containing Anthropic-API calls. The canary-audit architecture (Sonnet-watches-Opus) is designed around this constraint: external enforcement is local git hooks + scheduled tasks invoking headless Claude Code, NOT GitHub Actions calling the API. Source: user directive 2026-05-25 (memory `feedback_no_apis_only_claude_max`), re-affirming previous statements across multiple sessions. Failure to follow this rule is the same severity as failing "LEAVE NOTHING BEHIND."
|
|
81
90
|
- **LEAVE NOTHING BEHIND.** If you see an issue — any issue, however small, whether pre-existing, already-known, "out of scope", or unrelated to the task at hand — you fix it in the same run. "Pre-existing", "known", "not introduced by this change", and "out of scope" are NEVER grounds to leave a defect in place; noticing a defect and walking past it is itself a defect. Two — and only two — exceptions: **(a)** the fix needs a product or architecture decision only the user can make — surface it explicitly and immediately, never bury it; **(b)** the fix is genuinely too entangled to land in the current change — then it is queued as an *immediate next cycle in the same run*, never as "later", never as a handoff-doc footnote. Never end a task, cycle, or run with a known issue unaddressed. Established by user directive 2026-05-14, re-affirmed 2026-05-15, 2026-05-17, and **2026-05-19** ("doesn't matter pre-existing or not; this directive holds for all future runs, every skill"). This generalizes and strengthens the rule-0e family (memory `feedback_fix_every_bug_found`). See the prominent top-of-file statement under "Working directive — LEAVE NOTHING BEHIND".
|
|
91
|
+
- **KEEP THE APP PIN IN SYNC WITH THE LIBRARY — when we bump the package, we bump the app.** The `@v<VERSION>` docpluck pin in `PDFextractor/service/requirements.txt` (on docpluckapp `origin/master`, the production-authoritative source) MUST always equal the library's latest released `v*` tag; a lagging pin silently runs the old library in production. `bump-app-pin.yml` auto-bumps it on tag push but is **best-effort and has missed silently** — never assume, always verify with `python scripts/check_app_pin_sync.py` (exit 0 = synced). A stale LOCAL clone shows an old pin even when prod is synced, so the gate reads `origin/master`, never a local file. Wired into `/docpluck-qa`, `/docpluck-review`, `/docpluck-deploy`. Full mechanism + recovery under "Two-Repo Architecture → Library ↔ app version sync". Established by user directive 2026-06-20.
|
|
82
92
|
- **EVERY FIX MUST BE GENERAL — serve all future PDFs, never a one-PDF quick-hack.** docpluck is a meta-science tool that processes arbitrary academic PDFs across many publishers. Every change must be keyed on a STRUCTURAL SIGNATURE — a typographic pattern, layout invariant, glyph-corruption shape, section-structure rule — never on paper identity, filename, or a string hard-coded from one PDF. A change that resolves one paper's quirk but risks regressions on others is the WRONG fix; find the general root cause. Regression tests use specific PDF fixtures, but the fix *logic* must generalize to any PDF with the same structural signature. Always run the full 26-paper baseline to confirm no regression; widen verification (broad-read, more AI-golds) when a fix touches a shared code path. Established by user directive 2026-05-15. See memory `feedback_general_fixes_not_pdf_specific`.
|
|
83
93
|
- **NEVER swap the PDF text-extraction tool as a fix for downstream problems.** The TEXT channel is `extract_pdf` (pdftotext default mode); the LAYOUT channel is `extract_pdf_layout` (pdfplumber). They are not interchangeable text sources. Sections / normalize / batch consume the text channel; tables / figures / F0-layout-strip consume the layout channel. Real-world-paper bugs (watermarks in body, abstract not detected, column interleaving) must be fixed in the layer that owns the artifact (`normalize.py` W0, `sections/annotators/text.py`, `sections/taxonomy.py`, `sections/core.py`) — not by switching extraction tools. See [LESSONS.md L-001](./LESSONS.md#l-001--never-swap-the-pdf-text-extraction-tool-as-a-fix-for-downstream-problems) for the full incident record.
|
|
84
94
|
- **NEVER use pdftotext with `-layout` flag** — causes column interleaving. See `docpluck/extract.py:13–16` and [LESSONS.md L-002](./LESSONS.md#l-002--never-use-pdftotext--layout-flag).
|
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
This file tracks future-aim items that are scoped out of the current milestone but should not be lost. See `docs/superpowers/specs/` for active specs.
|
|
4
4
|
|
|
5
|
+
## 2026-06-21 — v2.4.95 corpus assessment (7/7 canary FAIL) — RC-B7 done, RC-T spec'd + handed off, RC-1 next
|
|
6
|
+
|
|
7
|
+
> Full real AI-verify of 7 canaries at HEAD v2.4.95 = **7/7 FAIL** on 3 architectural root causes (the canary-audit hook's `AUDIT_DEFERRED→union PASS` had masked it again — `feedback_canary_audit_clobbers_phase5d`). Canonical queue: [`docs/TRIAGE_2026-06-21_head_v2.4.95_assessment.md`](docs/TRIAGE_2026-06-21_head_v2.4.95_assessment.md). Branch `feat/rc-t-table-region-guard` (commits `db7192b`, `927d869`).
|
|
8
|
+
|
|
9
|
+
- [x] **RC-B7 deleted-minus glyph — DONE (W0h).** Already implemented as `normalize.recover_dropped_minus_via_layout` (wired render.py:5079→normalize.py:3170, tested `tests/test_dropped_minus_layout_recovery_real_pdf.py`); HEAD recovers 4/5 ar_apa betas. Residuals (`.245` pixel-minus, β→b) are OCR-tier won't-fix (both pdftotext AND pdfplumber agree on the wrong glyph). The cycle-3 ar_apa "FAIL" was a verifier over-flag.
|
|
10
|
+
- [ ] **RC-T table-region prose contamination — SPEC'd, implementation handed off.** Widest defect (all 7 papers): a small table among prose gets a near-full-page region (ip_feldman T10 region 71→331 reaches into the Discussion prose) and the whitespace clusterer turns prose into "rows" → garbage cells, orphan `### Table N`, empty shells, duplicate dumps. Two layers: (1) Camelot free-form (no `table_areas`) → whole-page bbox; (2) `caption + SEARCH_BELOW_PT(250)` region with no table-END detection. Fix = region prose-trim + degenerate-region guard, keyed on **cell content not bbox-size**. Spec: [`docs/superpowers/specs/2026-06-21-rc-t-table-region-prose-contamination.md`](docs/superpowers/specs/2026-06-21-rc-t-table-region-prose-contamination.md). Handoff: see `docs/superpowers/handoffs/2026-06-21-*`.
|
|
11
|
+
- [ ] **RC-1 region-aware columns — after RC-T.** Two-column + PLOS-sidebar interleave (chan_feldman, chandrashekar, plos_med front-matter-before-Abstract). Step-2 band path exists ship-dark; see the existing RC-1 entry below + `docs/superpowers/specs/2026-06-08-rc1-region-aware-column-architecture.md`.
|
|
12
|
+
- [ ] **article-finder: correct the `collabra.77859` gold table-numbering** (gold says "Table 2"; the source PDF caption + docpluck + the consumer all say "Table 3" — gold error, see below).
|
|
13
|
+
|
|
14
|
+
## 2026-06-20 — v2.4.95 SHIPPED (REQUEST_11: flatten fields for non-clinical result tables) — deferred follow-ups
|
|
15
|
+
|
|
16
|
+
> ✅ **Shipped + verified in prod.** main `370b89c` + tag `v2.4.95` (canary PASS), PyPI published, app pin auto-bumped to `@v2.4.95`, Railway `/health` reports `docpluck_version: 2.4.95`. Closes ESCImate REQUEST_11 (blank-header column-role recovery + packed parallel-arm split + general U+2212/bracket-CI fixes). All 4 acceptance criteria met, both target papers AI-gold-verified. Details: `REPLY_FROM_DOCPLUCK_v2.4.95.md`, `docs/HANDOFF_2026-06-20_request11_flatten_nonclinical_tables.md`, `CHANGELOG.md`.
|
|
17
|
+
|
|
18
|
+
- [ ] **`fields.effect_type` — opt-in, only if ESCImate requests it.** REQUEST_11 §2.4 asked for `effect_type` (cohens_d / pearson_r / partial_eta_squared / mean_difference) but called it "not a blocker." Deferred because emitting it would add a key to PROSECCO's 6 rows, conflicting with acceptance #4 (PROSECCO byte-identical). Offered as an opt-in in the reply; implement (grounded in key-present + effect vocab) only if the consumer accepts the PROSECCO field-set change.
|
|
19
|
+
- [x] **(ADJUDICATED 2026-06-21 — docpluck CORRECT, gold mis-numbered) `collabra.77859` caption-number "Table 3" vs gold "Table 2".** Resolved via the source text channel: the PDF's own caption at text-line 866 reads verbatim `Table 3. Study 4: Dish sets` (tables run 1→5 sequentially: L282 T1, L680 T2, **L866 T3**, L869 T4, L1034 T5). docpluck binds "Table 3" exactly matching the source; the consumer also calls it Table 3. **The AI gold mis-numbered it "Table 2"** — gold error, not a docpluck defect. → surface to article-finder for gold correction (logged above).
|
|
20
|
+
- [x] **(no action) `collabra.90203` Table 10 "Joint/No-explicit" r=.59 vs gold .63** — the PDF *text layer* encodes `.59` (pdftotext AND Camelot agree); only the AI-visual gold sees `.63`. Source text-layer corruption, undetectable without OCR (not allowed). Documented for the consumer; nothing docpluck can fix.
|
|
21
|
+
- [x] **(handled) Value-exact real-PDF flatten tests flake under `-n10`** — Camelot extraction is non-deterministic under parallel load; the 4 `*_real_pdf` tests now skip under `PYTEST_XDIST_WORKER` (run serially in canonical QA), matching the `test_benchmark_docx_html.py` convention. Synthetic-grid contract tests cover the logic under `-n10`.
|
|
22
|
+
|
|
5
23
|
## 2026-06-16 — deferred for investigation before code changes
|
|
6
24
|
|
|
7
25
|
- [ ] **Investigate `sections=` extraction de-dup (no behavior change yet).** `extract_pdf(..., sections=...)`, `extract_docx(..., sections=...)`, and `extract_html(..., sections=...)` currently do one extraction pass and then call `extract_sections(...)`, which can re-run extraction/annotation internally by design. Before optimizing, document invariants proving parity with direct `extract_sections(...)` outputs, then run corpus/harness verification to confirm zero regressions. No implementation change until those proofs are in place.
|
|
@@ -78,7 +78,7 @@ from .figures import Figure
|
|
|
78
78
|
from .extract_structured import TABLE_EXTRACTION_VERSION, StructuredResult, extract_pdf_structured
|
|
79
79
|
from .render import render_pdf_to_markdown
|
|
80
80
|
|
|
81
|
-
__version__ = "2.4.
|
|
81
|
+
__version__ = "2.4.97"
|
|
82
82
|
__author__ = "Gilad Feldman"
|
|
83
83
|
__license__ = "MIT"
|
|
84
84
|
|
|
@@ -37,7 +37,7 @@ from .tables.render import cells_to_html
|
|
|
37
37
|
from .telemetry import record_fallback
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
TABLE_EXTRACTION_VERSION = "2.4.
|
|
40
|
+
TABLE_EXTRACTION_VERSION = "2.4.2" # v2.4.2 (RC-T Layer-2): _extract_table_body_text now (a) Note-anchor — a table's "Note:" footnote is its last element, so trim body prose bled past it (chan_feldman T1/T3, efendic_2022 T5); and (b) degenerate-prose guard — suppress a raw_text fallback that STARTS mid-sentence with a lowercase multi-letter word AND is majority sentence-shaped prose, so render emits a clean caption-only table instead of an unstructured-table dump duplicating Results/Discussion prose (chan_feldman T9 was a verbatim ## Discussion duplicate). FP-safe (real cells start with header/label/number/single-letter marker, never a wrapped continuation); full-corpus 101-PDF guard-diff only trims+suppresses (grew=0 changed=0). # v2.4.1 (DP-2/DP-5): (DP-2) blank-header role recovery now types the unlabeled p-value (a bare `.XXX` after the test stat, no comparison op) and df (a bare integer/Welch-decimal between the stat and the d[CI] column) columns it previously skipped — collabra.77859 T3 fields gain p+df (tables.flatten._recover_blank_roles Pass 4.5). (DP-5) parallel-arm tables with a TWO-ROW header no longer drop their first data row, and a CENTERED super-header is aligned to its arm block instead of its visual-center column: (a) cell_cleaning._is_header_like_row counts APA value shapes (leading-dot decimal, bracketed CI, operator-prefixed p, N/A) as data via _DATA_VALUE_CELL_RE so a real first data row isn't read as a 3rd header row (collabra.90203 T10 recovered the Identifiable/Explicit-learning correlation); (b) tables.flatten._detect_column_groups re-derives arm boundaries from equal-width blocks of the data region (each must hold one super-label) so a centered super-label folded mid-span no longer swaps arm values (xiao_2021 T4 Original/Replication F) or pushes a stat column into the label region; (c) tables.flatten._classify_column reads a folded super-header cell's role from its sub-part (collabra.90203 T10 CI). Full-corpus cached-table flatten diff: no clean-table regression. # v2.4.0 (REQUEST_11): flatten now populates fields for NON-clinical result tables — (a) blank-header column-role recovery (tables.flatten._recover_blank_roles): assign a stat role to a header-stripped column from its data-token SHAPE (CI brackets, df1/df2 pair, estimate-adjacent-CI, p-with-operator) AND caption/footnote/all-header-rows vocabulary, never bare position; recovers collabra.77859 T5 (t/df/d/CI) + collabra.90203 T8/T9 (F/df/p/BF01/eta²p-as-est/CI). (b) packed parallel-arm split (tables.flatten._detect_packed_arms/_flatten_packed_arms): tables packing k≥2 arms into single cells ("Separate Joint" + space-joined values) emit one typed record per arm (group=arm) — collabra.77859 T3 Separate/Joint, xiao_2021 T7 Regret/Justifiability. (c) new BF01 role; validity guards drop r∉[-1,1] / non-monotone CI / non-int n / p∉[0,1]. (d) GENERAL L-004 fixes: _parse_number + _parse_ci_cell fold U+2212 MINUS (negative t/d/CI bounds in Camelot cells were dropped/sign-lost); _VALUE_GROUP_RE handles bracket-led CI groups. Default render + PROSECCO output byte-identical. # v2.3.0 (Tier-2, REQUEST_10): cross-flavor lattice-augmentation — recover data rows a lattice extraction vertically TRUNCATED by appending the rows a same-page, same-column-count stream table captured below the lattice bbox (camelot_extract._augment_lattice_with_stream_rows), gated on equal-col-count + bbox overlap + extends-below; PLUS numeric/parenthetical continuation merge (cell_cleaning._merge_continuation_rows) rejoining stream's stacked value/parenthetical cells. Fixes PROSECCO Table 2 R2-R6. v2.2.0: EC-T1 docpluck.tables.flatten — per-row FlattenedRow records (sentence + structured fields) for downstream stat-verification consumers (effectcheck/escimate/scimeto) + opt-in inline "rendered as text" block below each <table> via render_pdf_to_markdown(flatten_tables_inline=True). v2.1.5: cell-cleaning recovers CMEX10 extensible-bracket PUA glyphs (U+F8EE-F8FB). v2.1.4: cell-cleaning recovers Adobe-Symbol-font PUA glyphs (beta/chi/bullet as U+F0xx). v2.1.3: cell-cleaning recovers '<'-as-backslash glyph corruption. v2.1.2: cell-cleaning recovers descending-CI '2'-for-minus corruption. v2.1.1: cell-cleaning recovers (cid:0) corrupted minus signs + strips math-alphanumeric styling. v2.1.0: cell-cleaning pipeline ported from splice spike (multi-row header detection, continuation merging, leader-dot strip, mash-split, group separators, sig-marker attach)
|
|
41
41
|
|
|
42
42
|
TableTextMode = Literal["raw", "placeholder"]
|
|
43
43
|
|
|
@@ -1306,6 +1306,74 @@ def _line_is_body_prose(line: str) -> bool:
|
|
|
1306
1306
|
return stopwords_hit >= 4
|
|
1307
1307
|
|
|
1308
1308
|
|
|
1309
|
+
def _join_wrapped_lines(lines: list[str]) -> list[str]:
|
|
1310
|
+
"""Merge pdftotext-wrapped lines into logical paragraphs.
|
|
1311
|
+
|
|
1312
|
+
pdftotext linearizes a flowing prose paragraph into several short
|
|
1313
|
+
(~45-60 char) lines; the per-line ``_line_is_body_prose`` gate
|
|
1314
|
+
(len >= 80) cannot see prose in that wrapped form. Joining a line with
|
|
1315
|
+
the next whenever it does not end on sentence-terminal punctuation
|
|
1316
|
+
reconstructs the paragraph so prose can be measured at paragraph scale.
|
|
1317
|
+
"""
|
|
1318
|
+
paras: list[str] = []
|
|
1319
|
+
cur = ""
|
|
1320
|
+
for ln in lines:
|
|
1321
|
+
s = ln.strip()
|
|
1322
|
+
if not s:
|
|
1323
|
+
continue
|
|
1324
|
+
cur = (cur + " " + s).strip() if cur else s
|
|
1325
|
+
if s.endswith((".", "!", "?", ":")):
|
|
1326
|
+
paras.append(cur)
|
|
1327
|
+
cur = ""
|
|
1328
|
+
if cur:
|
|
1329
|
+
paras.append(cur)
|
|
1330
|
+
return paras
|
|
1331
|
+
|
|
1332
|
+
|
|
1333
|
+
def _raw_text_is_degenerate_prose(text: str) -> bool:
|
|
1334
|
+
"""True if a table raw_text fallback is dominated by flowing body prose.
|
|
1335
|
+
|
|
1336
|
+
RC-T Layer-2 (v2.4.97). When Camelot recovers no cells AND the
|
|
1337
|
+
caption-anchored region has no extractable table text near the caption,
|
|
1338
|
+
the body_start walk lands INSIDE a prose paragraph and the fallback
|
|
1339
|
+
swallows Results/Discussion prose (which is then duplicated under its
|
|
1340
|
+
real section heading). Such a block must be suppressed (render then
|
|
1341
|
+
emits a clean caption-only table) rather than dumped verbatim.
|
|
1342
|
+
|
|
1343
|
+
FP-safe by construction — fires only when BOTH hold:
|
|
1344
|
+
(a) the block STARTS mid-sentence: its first line begins with a
|
|
1345
|
+
lowercase multi-letter continuation word. A real table's
|
|
1346
|
+
linearized cells start with a column header, label, number, or a
|
|
1347
|
+
single-letter item marker (``a``/``b``/``c``) — never a wrapped
|
|
1348
|
+
mid-paragraph continuation like "than empathy. We provided ...".
|
|
1349
|
+
(b) the joined block is majority (>= 60% of chars) sentence-shaped
|
|
1350
|
+
body prose.
|
|
1351
|
+
|
|
1352
|
+
Legitimate degraded tables are preserved: hypotheses ("a There is a
|
|
1353
|
+
positive association ..."), descriptive rows ("Median age (years)"),
|
|
1354
|
+
instrument items ("h et al., 1997)") all fail (a). Keyed purely on the
|
|
1355
|
+
structural overshoot signature, never on paper identity.
|
|
1356
|
+
"""
|
|
1357
|
+
lines = [ln for ln in text.split("\n") if ln.strip()]
|
|
1358
|
+
if len(lines) < 4:
|
|
1359
|
+
return False
|
|
1360
|
+
first_tokens = lines[0].split()
|
|
1361
|
+
first_word = first_tokens[0] if first_tokens else ""
|
|
1362
|
+
starts_midsentence = (
|
|
1363
|
+
len(first_word) >= 2
|
|
1364
|
+
and first_word[0].islower()
|
|
1365
|
+
and first_word[0].isalpha()
|
|
1366
|
+
)
|
|
1367
|
+
if not starts_midsentence:
|
|
1368
|
+
return False
|
|
1369
|
+
paragraphs = _join_wrapped_lines(lines)
|
|
1370
|
+
total = sum(len(p) for p in paragraphs)
|
|
1371
|
+
if total == 0:
|
|
1372
|
+
return False
|
|
1373
|
+
prose = sum(len(p) for p in paragraphs if _line_is_body_prose(p))
|
|
1374
|
+
return prose >= 0.6 * total
|
|
1375
|
+
|
|
1376
|
+
|
|
1309
1377
|
def _extract_table_body_text(
|
|
1310
1378
|
raw_text: str,
|
|
1311
1379
|
cap: CaptionMatch,
|
|
@@ -1379,6 +1447,31 @@ def _extract_table_body_text(
|
|
|
1379
1447
|
break
|
|
1380
1448
|
kept.append(ln)
|
|
1381
1449
|
|
|
1450
|
+
# Note-anchor table-end (RC-T Layer-2, v2.4.97). A table's "Note:" /
|
|
1451
|
+
# "Notes:" footnote is, by academic-table convention, its LAST element.
|
|
1452
|
+
# Any text after the note paragraph is body prose that bled past the
|
|
1453
|
+
# table boundary — the caption-anchored region overshot the table end
|
|
1454
|
+
# and the per-line `_line_is_body_prose` gate (len >= 80) misses prose
|
|
1455
|
+
# that pdftotext WRAPPED into short (~48-char) lines, so it accumulates
|
|
1456
|
+
# here. Trim everything after the note's (possibly wrapped) paragraph.
|
|
1457
|
+
# This is FP-safe: legitimate table cells (hypotheses a/b/c, instrument
|
|
1458
|
+
# items) appear BEFORE the note; nothing legitimate follows it. Keyed on
|
|
1459
|
+
# the structural "Note: ... <sentence end>" signature, never paper
|
|
1460
|
+
# identity. `^Notes?[.:]` requires punctuation so body prose that merely
|
|
1461
|
+
# starts with the word "Note that ..." does not false-trigger.
|
|
1462
|
+
note_idx = next(
|
|
1463
|
+
(i for i, ln in enumerate(kept)
|
|
1464
|
+
if re.match(r"^\s*Notes?[.:]", ln.strip())),
|
|
1465
|
+
None,
|
|
1466
|
+
)
|
|
1467
|
+
if note_idx is not None and not os.environ.get("DOCPLUCK_RCT_L2_BYPASS"):
|
|
1468
|
+
note_end = note_idx
|
|
1469
|
+
for k in range(note_idx, len(kept)):
|
|
1470
|
+
note_end = k
|
|
1471
|
+
if kept[k].strip().endswith((".", "!", "?")):
|
|
1472
|
+
break
|
|
1473
|
+
kept = kept[: note_end + 1]
|
|
1474
|
+
|
|
1382
1475
|
# Trim trailing heading-like short lines that don't belong to this table
|
|
1383
1476
|
# (the start of the next section). Two patterns are trimmed:
|
|
1384
1477
|
# * Title-Case headings without a sentence terminator
|
|
@@ -1414,7 +1507,17 @@ def _extract_table_body_text(
|
|
|
1414
1507
|
s = re.sub(r"[ \t]+", " ", ln).strip()
|
|
1415
1508
|
if s:
|
|
1416
1509
|
cleaned_lines.append(s)
|
|
1417
|
-
|
|
1510
|
+
result = "\n".join(cleaned_lines).strip()
|
|
1511
|
+
# Degenerate-prose guard (RC-T Layer-2, v2.4.97): drop a raw_text
|
|
1512
|
+
# fallback that is really body prose the region overshot into, so the
|
|
1513
|
+
# renderer emits a clean caption-only table instead of an
|
|
1514
|
+
# ``unstructured-table`` dump that duplicates Results/Discussion prose.
|
|
1515
|
+
# ``DOCPLUCK_RCT_L2_BYPASS`` reverts both Layer-2 additions (Note-anchor
|
|
1516
|
+
# + this guard) to HEAD behavior — used only by the FP-scan harness to
|
|
1517
|
+
# diff guard-live vs guard-bypassed over the full corpus.
|
|
1518
|
+
if not os.environ.get("DOCPLUCK_RCT_L2_BYPASS") and _raw_text_is_degenerate_prose(result):
|
|
1519
|
+
return ""
|
|
1520
|
+
return result
|
|
1418
1521
|
|
|
1419
1522
|
|
|
1420
1523
|
def _figure_from_caption(
|
|
@@ -2696,6 +2696,20 @@ def _strip_phantom_camelot_tables(text: str) -> str:
|
|
|
2696
2696
|
# Process each <table>...</table> block independently.
|
|
2697
2697
|
pattern = re.compile(r"<table\b[^>]*>.*?</table>\s*", re.DOTALL | re.IGNORECASE)
|
|
2698
2698
|
|
|
2699
|
+
# RC-T (v2.4.96): token sets of the document's italic caption lines (``*…*``).
|
|
2700
|
+
# Used by the scoped "the" body-prose path below to EXCLUDE a <th> that is
|
|
2701
|
+
# the table's own TITLE leaked into the header (a title-leak on a REAL table,
|
|
2702
|
+
# e.g. amp_1 Table 5) rather than absorbed body prose — high caption-token
|
|
2703
|
+
# overlap == title-leak == keep. Built once per render; empty when a paper
|
|
2704
|
+
# has no captions (then the scoped path simply never excludes).
|
|
2705
|
+
_caption_tok_sets = [
|
|
2706
|
+
toks for toks in (
|
|
2707
|
+
set(re.findall(r"[a-z]{2,}", c.lower()))
|
|
2708
|
+
for c in re.findall(r"\*([^*\n]{12,}?)\*", text)
|
|
2709
|
+
)
|
|
2710
|
+
if len(toks) >= 4
|
|
2711
|
+
]
|
|
2712
|
+
|
|
2699
2713
|
def is_phantom(block: str) -> bool:
|
|
2700
2714
|
# Extract <th> contents.
|
|
2701
2715
|
th_cells = re.findall(r"<th[^>]*>(.*?)</th>", block, re.DOTALL | re.IGNORECASE)
|
|
@@ -2752,6 +2766,31 @@ def _strip_phantom_camelot_tables(text: str) -> str:
|
|
|
2752
2766
|
if fn_count >= 3 and verb_count >= 2:
|
|
2753
2767
|
th_section_leak = True
|
|
2754
2768
|
break
|
|
2769
|
+
# RC-T (v2.4.96, 2026-06-21): "the" — the single most common
|
|
2770
|
+
# English function word — is absent from _FUNCTION_WORDS_IN_PROSE,
|
|
2771
|
+
# so a body-prose <th> with exactly fn_count==2 + "the" slipped
|
|
2772
|
+
# under the bar: maier_2023 Table 7 "Following the analyses
|
|
2773
|
+
# conducted in Study 1 of Small" (fn=in,of; verb=following,
|
|
2774
|
+
# conducted) and chan_feldman Table 6 "associations between the
|
|
2775
|
+
# six measures of interest: …". This NEW path counts "the" to push
|
|
2776
|
+
# such ths over — but is SCOPED to the marginal case (fn_count<3
|
|
2777
|
+
# without "the", >=3 with it) so every table already stripped at
|
|
2778
|
+
# HEAD via the fn>=3 path stays byte-identical, AND gated on NOT
|
|
2779
|
+
# being a title-leak: amp_1 Table 5 leaks its own caption
|
|
2780
|
+
# ("Improving Scholarly Impact … Practice") into the <th> over a
|
|
2781
|
+
# REAL grid — stripping it would destroy a real table. A title
|
|
2782
|
+
# leak shares most of its tokens with a caption; body prose
|
|
2783
|
+
# shares ~none.
|
|
2784
|
+
the_count = sum(1 for w in words if w == "the")
|
|
2785
|
+
if fn_count < 3 and (fn_count + the_count) >= 3 and verb_count >= 2:
|
|
2786
|
+
th_toks = set(re.findall(r"[a-z]{2,}", cleaned_th.lower()))
|
|
2787
|
+
is_title_leak = bool(th_toks) and any(
|
|
2788
|
+
len(th_toks & cap) / len(th_toks) >= 0.6
|
|
2789
|
+
for cap in _caption_tok_sets
|
|
2790
|
+
)
|
|
2791
|
+
if not is_title_leak:
|
|
2792
|
+
th_section_leak = True
|
|
2793
|
+
break
|
|
2755
2794
|
word_set = set(words)
|
|
2756
2795
|
if any(t.lower() in word_set for t in _PHANTOM_TABLE_BODY_LEAK_TOKENS):
|
|
2757
2796
|
if verb_count >= 1:
|
|
@@ -393,13 +393,32 @@ _NUMERIC_CELL_RE = re.compile(
|
|
|
393
393
|
r"^[-−–]?\d+(?:[.,]\d+)*(?:[%∗*]+)?(?:\s*\([^)]*\))?$"
|
|
394
394
|
)
|
|
395
395
|
|
|
396
|
+
# A cell carrying a statistic VALUE (vs a header label). Broader than
|
|
397
|
+
# _NUMERIC_CELL_RE: also matches APA leading-dot decimals (".34"), operator-
|
|
398
|
+
# prefixed p-values ("< .001"), bracketed numeric intervals ("[0.53, 0.72]"),
|
|
399
|
+
# and the "N/A" filler — all DATA, not header text. The interval branch requires
|
|
400
|
+
# a digit and NO letters inside the brackets so a genuine header cell like
|
|
401
|
+
# "[95% CI]" (letters present) is NOT counted as data and stays a header. Used by
|
|
402
|
+
# `_is_header_like_row` so a real data row whose APA-formatted values the bare
|
|
403
|
+
# numeric pattern under-counted is not mistaken for an extra header row — the
|
|
404
|
+
# bug that silently dropped the FIRST data row of two-header-row correlation
|
|
405
|
+
# tables (collabra.90203 Table 10, DP-5).
|
|
406
|
+
_DATA_VALUE_CELL_RE = re.compile(
|
|
407
|
+
r"^(?:"
|
|
408
|
+
r"[<>=]?\s*[-−–]?\d*[.,]?\d+(?:[.,]\d+)*(?:[%∗*]+)?(?:\s*\([^)]*\))?"
|
|
409
|
+
r"|\[[^\]A-Za-z]*\d[^\]A-Za-z]*\]"
|
|
410
|
+
r"|n\s*/?\s*a"
|
|
411
|
+
r")$",
|
|
412
|
+
re.I,
|
|
413
|
+
)
|
|
414
|
+
|
|
396
415
|
|
|
397
416
|
def _is_header_like_row(row: list[str]) -> bool:
|
|
398
417
|
"""Heuristic: a row that looks like part of a header rather than data."""
|
|
399
418
|
nonempty = [c.strip() for c in row if (c or "").strip()]
|
|
400
419
|
if not nonempty:
|
|
401
420
|
return False
|
|
402
|
-
numeric = sum(1 for c in nonempty if
|
|
421
|
+
numeric = sum(1 for c in nonempty if _DATA_VALUE_CELL_RE.match(c))
|
|
403
422
|
if numeric / len(nonempty) > 0.3:
|
|
404
423
|
return False
|
|
405
424
|
avg_len = sum(len(c) for c in nonempty) / len(nonempty)
|