实现 arXiv 提交边栏戳清洗组件

冻结 0004,新增严格整行删除组件和测试,并记录 5 份论文的只读验证结果。同步 ClinDB 清洗范围,并忽略本地 reference 调研副本。
This commit is contained in:
2026-08-22 15:02:21 +08:00
parent 3edeeaf30e
commit 8c23ac5521
9 changed files with 580 additions and 33 deletions
+155
View File
@@ -0,0 +1,155 @@
from __future__ import annotations
import pytest
import mdpolish
from mdpolish import Pipeline, RunStatus
from mdpolish.components import ArxivSubmissionStampComponent
STAMP = "arXiv:2104.12345v2 [stat.ME] 31 Dec 2021"
OTHER_STAMP = "arXiv:2301.7v1 [cs.AI] 1 Jan 2023"
REASON = "删除完整匹配的 arXiv 提交边栏戳"
def transform(markdown: str) -> mdpolish.TransformResult:
return Pipeline([ArxivSubmissionStampComponent()]).transform(markdown)
def test_component_metadata_and_package_export() -> None:
result = transform("")
assert result.status is RunStatus.SUCCESS
assert result.components[0].component_id == "paper.arxiv_submission_stamp"
assert result.components[0].version == "1.0.0"
assert result.components[0].parameters == ()
assert result.components[0].applicability
assert not hasattr(mdpolish, "ArxivSubmissionStampComponent")
@pytest.mark.parametrize("markdown", ["", "普通正文", "中文\nCafe\u0301\n🙂\n"])
def test_no_target_returns_unchanged_success(markdown: str) -> None:
result = transform(markdown)
assert result.status is RunStatus.SUCCESS
assert result.output_markdown == markdown
assert result.changes == ()
@pytest.mark.parametrize(
("markdown", "expected"),
[
(f"{STAMP}\n正文", "正文"),
(f"正文\n{STAMP}\n后文", "正文\n后文"),
(f"正文\n{STAMP}", "正文\n"),
(STAMP, ""),
(f"正文\r\n{STAMP}\r\n后文", "正文\r\n后文"),
(f"正文\r{STAMP}\r后文", "正文\r后文"),
],
)
def test_deletes_target_at_approved_line_boundaries(markdown: str, expected: str) -> None:
result = transform(markdown)
assert result.status is RunStatus.SUCCESS
assert result.output_markdown == expected
assert len(result.changes) == 1
def test_multiple_targets_are_reported_in_source_order_with_one_atomic_batch() -> None:
markdown = f"{STAMP}\n保留\n{OTHER_STAMP}"
result = transform(markdown)
assert result.status is RunStatus.SUCCESS
assert result.output_markdown == "保留\n"
assert [change.before for change in result.changes] == [f"{STAMP}\n", OTHER_STAMP]
assert [change.proposal_ref.proposal_index for change in result.changes] == [0, 1]
assert [change.span.start for change in result.changes] == sorted(change.span.start for change in result.changes)
assert {change.before_sha256 for change in result.changes} == {result.input_sha256}
assert {change.after_sha256 for change in result.changes} == {result.current_sha256}
def test_adjacent_targets_use_non_overlapping_delete_ranges() -> None:
result = transform(f"{STAMP}\n{OTHER_STAMP}")
assert result.status is RunStatus.SUCCESS
assert result.output_markdown == ""
assert len(result.changes) == 2
assert result.changes[0].span.end == result.changes[1].span.start
@pytest.mark.parametrize(
"line",
[
f" {STAMP}",
f"{STAMP} ",
f"- {STAMP}",
f"> {STAMP}",
"1. Example. arXiv preprint arXiv:2104.12345v2 [stat.ME], 2021.",
"See arXiv:2104.12345v2 for details.",
"arXiv:2104.12345 [stat.ME] 31 Dec 2021",
"arXiv:hep-ph/9901001 [hep-ph] 31 Dec 1999",
"arXiv:2104.12345v2 31 Dec 2021",
"arXiv:2104.12345v2 [stat.ME] 0 Dec 2021",
"arXiv:2104.12345v2 [stat.ME] 32 Dec 2021",
"arXiv:2104.12345v2 [stat.ME] 31 December 2021",
"arXiv:2104.12345v2 [统计] 31 Dec 2021",
],
)
def test_similar_arxiv_text_is_preserved(line: str) -> None:
markdown = f"前文\n{line}\n后文"
result = transform(markdown)
assert result.status is RunStatus.SUCCESS
assert result.output_markdown == markdown
assert result.changes == ()
def test_matching_line_inside_fenced_code_is_not_protected() -> None:
markdown = f"```text\n{STAMP}\n```\n"
result = transform(markdown)
assert result.status is RunStatus.SUCCESS
assert result.output_markdown == "```text\n```\n"
assert len(result.changes) == 1
def test_change_audit_records_identity_reason_source_and_batch_hashes() -> None:
result = transform(f"{STAMP}\n正文")
assert result.status is RunStatus.SUCCESS
change = result.changes[0]
assert change.component_id == "paper.arxiv_submission_stamp"
assert change.component_version == "1.0.0"
assert change.component_position == 0
assert change.proposal_ref.component_position == 0
assert change.proposal_ref.proposal_index == 0
assert change.edit_index == 0
assert change.reason == REASON
assert change.before == f"{STAMP}\n"
assert change.after == ""
assert change.before_sha256 == result.input_sha256
assert change.after_sha256 == result.current_sha256
def test_successful_output_is_stable_and_second_run_has_no_changes() -> None:
pipeline = Pipeline([ArxivSubmissionStampComponent()])
first = pipeline.transform(f"{STAMP}\n正文")
assert first.status is RunStatus.SUCCESS
assert first.output_markdown == "正文"
second = pipeline.transform(first.output_markdown)
assert second.status is RunStatus.SUCCESS
assert second.output_markdown == "正文"
assert second.changes == ()
assert second.residual_proposals == ()
def test_same_input_produces_same_ordered_result() -> None:
pipeline = Pipeline([ArxivSubmissionStampComponent()])
markdown = f"{STAMP}\n正文\n{OTHER_STAMP}\n"
assert pipeline.transform(markdown) == pipeline.transform(markdown)