Delete openalex.py
Browse files- openalex.py +0 -88
openalex.py
DELETED
|
@@ -1,88 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
OpenAlex enrichment — journal metadata + peer percentile calculation.
|
| 3 |
-
Free API, no key required. Uses polite pool with mailto header.
|
| 4 |
-
"""
|
| 5 |
-
import httpx, asyncio
|
| 6 |
-
from typing import Optional
|
| 7 |
-
|
| 8 |
-
UA = "ResearchNavigator/1.0 (mailto:library@ku.ac.ae)"
|
| 9 |
-
BASE = "https://api.openalex.org"
|
| 10 |
-
|
| 11 |
-
def _norm_issn(raw: str) -> Optional[str]:
|
| 12 |
-
if not raw: return None
|
| 13 |
-
d = raw.strip().upper().replace(" ","").replace("-","")
|
| 14 |
-
if len(d) == 8:
|
| 15 |
-
return f"{d[:4]}-{d[4:]}"
|
| 16 |
-
return None
|
| 17 |
-
|
| 18 |
-
async def enrich_journal(name: str, issn: Optional[str]) -> Optional[dict]:
|
| 19 |
-
try:
|
| 20 |
-
async with httpx.AsyncClient(timeout=8.0) as client:
|
| 21 |
-
if issn:
|
| 22 |
-
clean = _norm_issn(issn) or issn.strip()
|
| 23 |
-
r = await client.get(f"{BASE}/sources/issn:{clean}", headers={"User-Agent": UA})
|
| 24 |
-
else:
|
| 25 |
-
r = await client.get(f"{BASE}/sources", params={"search": name, "per_page": 1}, headers={"User-Agent": UA})
|
| 26 |
-
if not r.is_success: return None
|
| 27 |
-
d = r.json()
|
| 28 |
-
s = d if issn else (d.get("results") or [None])[0]
|
| 29 |
-
if not s: return None
|
| 30 |
-
return {
|
| 31 |
-
"openalex_id": s.get("id"),
|
| 32 |
-
"display_name": s.get("display_name"),
|
| 33 |
-
"issn_l": s.get("issn_l"),
|
| 34 |
-
"issns": s.get("issn", []),
|
| 35 |
-
"host_organization": s.get("host_organization_name"),
|
| 36 |
-
"is_oa": s.get("is_oa"),
|
| 37 |
-
"is_in_doaj": s.get("is_in_doaj"),
|
| 38 |
-
"works_count": s.get("works_count"),
|
| 39 |
-
"cited_by_count": s.get("cited_by_count"),
|
| 40 |
-
"h_index": (s.get("summary_stats") or {}).get("h_index"),
|
| 41 |
-
"two_yr_mean_citedness": (s.get("summary_stats") or {}).get("2yr_mean_citedness"),
|
| 42 |
-
"homepage_url": s.get("homepage_url"),
|
| 43 |
-
"top_concept": ((s.get("x_concepts") or [{}])[0]).get("display_name"),
|
| 44 |
-
"top_concept_id": ((s.get("x_concepts") or [{}])[0]).get("id"),
|
| 45 |
-
}
|
| 46 |
-
except Exception as e:
|
| 47 |
-
print(f"[OpenAlex] enrich error for {name}: {e}")
|
| 48 |
-
return None
|
| 49 |
-
|
| 50 |
-
async def compute_percentile(openalex: dict) -> Optional[dict]:
|
| 51 |
-
try:
|
| 52 |
-
if not openalex or not openalex.get("top_concept_id"): return None
|
| 53 |
-
val = openalex.get("two_yr_mean_citedness")
|
| 54 |
-
if val is None: return None
|
| 55 |
-
concept_id = str(openalex["top_concept_id"]).split("/")[-1]
|
| 56 |
-
async with httpx.AsyncClient(timeout=10.0) as client:
|
| 57 |
-
r = await client.get(
|
| 58 |
-
f"{BASE}/sources",
|
| 59 |
-
params={"filter": f"concepts.id:{concept_id},type:journal",
|
| 60 |
-
"per_page": 200, "sort": "summary_stats.2yr_mean_citedness:desc"},
|
| 61 |
-
headers={"User-Agent": UA}
|
| 62 |
-
)
|
| 63 |
-
if not r.is_success: return None
|
| 64 |
-
peers = [s.get("summary_stats", {}).get("2yr_mean_citedness")
|
| 65 |
-
for s in r.json().get("results", [])]
|
| 66 |
-
peers = [v for v in peers if isinstance(v, (int, float))]
|
| 67 |
-
if len(peers) < 10: return None
|
| 68 |
-
below = sum(1 for v in peers if v < val)
|
| 69 |
-
pct = round(below / len(peers) * 100)
|
| 70 |
-
q = "Q1" if pct >= 75 else "Q2" if pct >= 50 else "Q3" if pct >= 25 else "Q4"
|
| 71 |
-
return {"percentile": pct, "quartile": q,
|
| 72 |
-
"peer_count": len(peers), "concept": openalex.get("top_concept")}
|
| 73 |
-
except Exception as e:
|
| 74 |
-
print(f"[OpenAlex] percentile error: {e}")
|
| 75 |
-
return None
|
| 76 |
-
|
| 77 |
-
async def enrich_all(journals: list) -> list:
|
| 78 |
-
"""Enrich a list of journal dicts in parallel."""
|
| 79 |
-
async def _enrich_one(j: dict) -> dict:
|
| 80 |
-
oa = await enrich_journal(j.get("name",""), j.get("issn"))
|
| 81 |
-
pct = await compute_percentile(oa) if oa else None
|
| 82 |
-
j["openalex"] = oa
|
| 83 |
-
j["oa_percentile"] = pct
|
| 84 |
-
# Supplement ISSN from OpenAlex if missing
|
| 85 |
-
if not j.get("issn") and oa:
|
| 86 |
-
j["issn"] = oa.get("issn_l") or (oa.get("issns") or [None])[0]
|
| 87 |
-
return j
|
| 88 |
-
return list(await asyncio.gather(*[_enrich_one(j) for j in journals]))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|