fix(graph): resolve cited_id to papers.id via openalex_id JOIN

build_citation_graph now does:
  SELECT c.citing_id, COALESCE(p.id, c.cited_id) AS cited_id
  FROM citations c
  LEFT JOIN papers p ON p.openalex_id = c.cited_id

Before: cited_id (OpenAlex IDs) never matched papers.id (DOIs) →
  13 cross-citations between ingested papers were invisible in graph.
After: in-KB papers use their canonical DOI key; dangling references
  keep their raw OpenAlex ID unchanged.

Test: test_cross_citation_uses_doi_when_in_kb (42 passed).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tarik Moussa
2026-06-15 07:15:21 +02:00
parent a1a6f4590b
commit 664600fdc7
2 changed files with 30 additions and 1 deletions

View File

@@ -31,6 +31,11 @@ def build_citation_graph(conn: Any) -> nx.DiGraph:
the citing paper references the cited paper. Both ingested papers the citing paper references the cited paper. Both ingested papers
and dangling targets (cited but not yet ingested) appear as nodes. and dangling targets (cited but not yet ingested) appear as nodes.
``cited_id`` in the citations table stores OpenAlex IDs; ``papers.id``
stores DOIs. A LEFT JOIN resolves cited papers that are in the KB to
their canonical DOI key via ``papers.openalex_id``. Dangling references
(not in KB) keep their raw OpenAlex ID.
Parameters Parameters
---------- ----------
conn: conn:
@@ -40,7 +45,13 @@ def build_citation_graph(conn: Any) -> nx.DiGraph:
------- -------
nx.DiGraph with every (citing_id, cited_id) pair as an edge. nx.DiGraph with every (citing_id, cited_id) pair as an edge.
""" """
rows = conn.execute("SELECT citing_id, cited_id FROM citations").fetchall() rows = conn.execute(
"""
SELECT c.citing_id, COALESCE(p.id, c.cited_id) AS cited_id
FROM citations c
LEFT JOIN papers p ON p.openalex_id = c.cited_id
"""
).fetchall()
g: nx.DiGraph = nx.DiGraph() g: nx.DiGraph = nx.DiGraph()
for row in rows: for row in rows:
g.add_edge(row["citing_id"], row["cited_id"]) g.add_edge(row["citing_id"], row["cited_id"])

View File

@@ -80,6 +80,24 @@ class TestBuildCitationGraph:
g = build_citation_graph(_make_conn(rows)) g = build_citation_graph(_make_conn(rows))
assert "not_in_papers" in g.nodes() assert "not_in_papers" in g.nodes()
def test_cross_citation_uses_doi_when_in_kb(self):
# Simulate DB COALESCE: cited_id already resolved to papers.id (DOI)
# for in-KB papers; dangling references keep their OpenAlex ID.
rows = [
{
"citing_id": "https://doi.org/10.1234/paper-a",
"cited_id": "https://doi.org/10.5678/paper-b",
},
{
"citing_id": "https://doi.org/10.5678/paper-b",
"cited_id": "https://openalex.org/W999",
},
]
g = build_citation_graph(_make_conn(rows))
assert g.has_edge("https://doi.org/10.1234/paper-a", "https://doi.org/10.5678/paper-b")
assert g.has_edge("https://doi.org/10.5678/paper-b", "https://openalex.org/W999")
assert g.number_of_nodes() == 3
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# citation_pagerank # citation_pagerank