File size: 1,986 Bytes
0ccfe4a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
"""Semantic Scholar API client for citation count enrichment."""
from __future__ import annotations

import time

import httpx

from config import SEMANTIC_SCHOLAR_BASE
from logging_config import get_logger

_logger = get_logger("ingestion.semantic_scholar")

_BATCH_SIZE = 500  # Semantic Scholar batch endpoint limit


def fetch_citation_counts(pmids: list[str]) -> dict[str, int]:
    """
    Fetch citation counts for a list of PMIDs via Semantic Scholar batch API.
    Returns {pmid: citation_count}. PMIDs with no S2 record are omitted.
    """
    if not pmids:
        return {}

    result: dict[str, int] = {}

    for i in range(0, len(pmids), _BATCH_SIZE):
        batch = pmids[i : i + _BATCH_SIZE]
        ids = [f"PMID:{pmid}" for pmid in batch]

        for attempt in range(3):
            try:
                resp = httpx.post(
                    f"{SEMANTIC_SCHOLAR_BASE}/paper/batch",
                    params={"fields": "citationCount,externalIds"},
                    json={"ids": ids},
                    timeout=30,
                )
                resp.raise_for_status()
                papers = resp.json()
                break
            except httpx.HTTPError as exc:
                if attempt == 2:
                    _logger.warning(f"Semantic Scholar batch failed: {exc}")
                    papers = []
                    break
                time.sleep(2 ** attempt)

        for paper in papers:
            if paper is None:
                continue
            ext = paper.get("externalIds") or {}
            pmid = ext.get("PubMed")
            count = paper.get("citationCount")
            if pmid and count is not None:
                result[str(pmid)] = int(count)

        # Free tier allows ~100 requests per 5 minutes; 1s delay keeps us safe
        time.sleep(1.0)

    _logger.info(
        "Semantic Scholar citation fetch",
        extra={"data": {"requested": len(pmids), "found": len(result)}},
    )
    return result