diff --git a/paperforge/worker/ocr_roles.py b/paperforge/worker/ocr_roles.py
index 67d4aab7..84b9951f 100644
--- a/paperforge/worker/ocr_roles.py
+++ b/paperforge/worker/ocr_roles.py
@@ -1128,6 +1128,16 @@ def assign_block_role(
evidence=[f"author byline on page 1, assigned as authors: {text[:60]}"],
)
+ # Explicit correspondence line → frontmatter_support (must run before generic noise rule)
+ if page_num == 1:
+ lower_txt = text.lower().strip()
+ if lower_txt.startswith(("correspondence", "corresponding author", "address for correspondence")):
+ return RoleAssignment(
+ role="frontmatter_support",
+ confidence=0.75,
+ evidence=[f"page-1 correspondence support: {text[:60]}"],
+ )
+
if re.search(r"(?:correspondence|orcid|@)", text.lower()) and len(text.split()) <= 20 and page_num == 1:
return RoleAssignment(
role="frontmatter_noise",
@@ -1280,6 +1290,14 @@ def assign_block_role(
page_num = block.get("page", 1) or 1
still_frontmatter = _page_still_frontmatter(page_blocks, page_num, page_height)
+ # Explicit page-1 correspondence line → frontmatter_support
+ if page_num == 1 and lower_txt.startswith(("correspondence", "corresponding author", "address for correspondence")):
+ return RoleAssignment(
+ role="frontmatter_support",
+ confidence=0.75,
+ evidence=[f"page-1 correspondence support: {text[:60]}"],
+ )
+
# Check for inline table HTML
if text.strip().lower().startswith("
assert title_like, "Expected a page-1 paper_title block in DWQQK2YB"
assert author_like, "Expected a page-1 authors block in DWQQK2YB"
+
+
+def test_caqnw9q2_page1_correspondence_is_not_frontmatter_noise(tmp_path: Path) -> None:
+ result = replay_production_pipeline("CAQNW9Q2", tmp_path)
+ blocks = result["structured_blocks"]
+ candidates = [
+ b for b in blocks
+ if b.get("page") == 1 and "correspondence" in str(b.get("text") or "").lower()
+ ]
+ assert candidates, "Expected a correspondence-related block on CAQNW9Q2 page 1"
+ assert any(b.get("role") == "frontmatter_support" for b in candidates)
+ assert not all(b.get("role") == "frontmatter_noise" for b in candidates)
diff --git a/tests/test_ocr_roles.py b/tests/test_ocr_roles.py
index abb01024..0f66ad7b 100644
--- a/tests/test_ocr_roles.py
+++ b/tests/test_ocr_roles.py
@@ -1494,3 +1494,18 @@ def test_highlights_label_does_not_swallow_following_body_paragraphs() -> None:
assert roles[0].role == "structured_insert_candidate"
assert roles[1].role == "body_paragraph"
assert roles[2].role == "body_paragraph"
+
+
+def test_page1_explicit_correspondence_line_is_frontmatter_support() -> None:
+ from paperforge.worker.ocr_roles import assign_block_role
+
+ block = {
+ "page": 1,
+ "block_label": "text",
+ "block_content": "Correspondence: Prof. Smith, Department of Orthopedics, email@example.org",
+ "block_bbox": [100, 1380, 980, 1450],
+ "page_width": 1200,
+ "page_height": 1600,
+ }
+ result = assign_block_role(block, [block], page_width=1200, page_height=1600)
+ assert result.role == "frontmatter_support"