Pull a paper's citation neighborhood from the public Semantic Scholar API (no key required), build a NetworkX directed citation graph, compute PageRank/community structure, view it as an interactive Plotly network.
"Who cites this paper, and what does it cite?" is a graph question, not a list question — PageRank identifies locally-influential papers, community detection surfaces thematic clusters, and the network layout itself shows structure a flat reference list can't.
Default seed is the author's own published paper — Sakata et al., Radiative cooling film enabled by droplet-like infrared hot spots via low-cost and scalable spray-coating process for tropical regions, Cell Reports Physical Science 2024. Real, public, verifiable data (same as what's on the author's Google Scholar profile) — any DOI works via the sidebar input.
No API key required or used. Semantic Scholar's public keyless tier is
rate-limited; fetch.py retries with exponential backoff on 429s and
caches every response locally (cache/, gitignored) so repeated runs and
Streamlit's rerun-on-every-interaction model don't hammer the API. A failed
fetch raises S2UnavailableError rather than silently returning an empty
network — an API outage is not the same fact as "this paper has no
citations."
fetch.py— Semantic Scholar API client: paper lookup, citations, references, retry/backoff, local JSON cachegraph.py— pure functions (no network access, fully unit-testable): build anetworkx.DiGraphfrom fetched records, compute PageRank/degree metrics, detect communities via greedy modularityapp.py— Streamlit UI: DOI input, interactive Plotly network (node size = PageRank, color = community, ★ = seed paper), sortable table of most-central papers
tests/test_graph.py uses hand-built fixture data (no live API calls —
offline, fast, deterministic):
- Edge direction correctness (citing → seed, seed → cited — easy to get backwards, tested explicitly)
- No duplicate nodes for a repeated paper
- PageRank sums to exactly 1.0
- In-degree matches the actual citation edge count
- No self-loops
- Every node lands in exactly one community
- Single-node graph doesn't crash metrics or community detection
pip install -r requirements.txt
pytest tests/ # 8 tests, all offline
streamlit run app.pycitation_network_analyzer/
├── fetch.py # Semantic Scholar API client + cache
├── graph.py # NetworkX graph build + metrics (pure, testable)
├── app.py # Streamlit UI
└── tests/test_graph.py # offline fixture-based graph-correctness tests