researchbee / scimago_index.py
nikeshn's picture
Upload 7 files
7d94330 verified
Raw
History Blame Contribute Delete
6.13 kB
"""
SCImago CSV loader β€” builds in-memory ISSN β†’ journal dict at startup.
Used PRIVATELY for quartile lookup only. Never exposed as branded data source in UI.
Changes from v1.0:
- _norm_issn removed β€” uses utils.norm_issn
- sourceid now included in ISSN index entries (was missing β€” broke verify_links)
"""
import csv
import re
import os
from typing import Optional
from utils import norm_issn
_scimago_cache: dict = {}
def load_scimago(csv_path: str = None) -> dict:
global _scimago_cache
if _scimago_cache:
return _scimago_cache
if csv_path is None:
csv_path = os.path.join(os.path.dirname(__file__), "scimagojr_2025.csv")
index = {}
try:
with open(csv_path, encoding="latin-1", newline="") as f:
reader = csv.DictReader(f, delimiter=",")
for row in reader:
issn_raw = row.get("Issn", "")
issn_parts = re.split(r"[,;\s]+", issn_raw)
entry = {
"title": row.get("Title", "").strip(),
"rank": row.get("Rank", "").strip(),
"quartile": row.get("SJR Best Quartile", "").strip(),
"h_index": row.get("H index", "").strip(),
"categories": row.get("Categories", "").strip(),
"areas": row.get("Areas", "").strip(),
"publisher": row.get("Publisher", "").strip(),
"open_access":row.get("Open Access", "No").strip(),
"country": row.get("Country", "").strip(),
"sourceid": row.get("Sourceid", "").strip(), # FIX: was missing
}
for part in issn_parts:
normed = norm_issn(part)
if normed:
index[normed] = entry
_scimago_cache = index
print(f"[SCImago] Loaded {len(index):,} ISSN entries.")
except Exception as e:
print(f"[SCImago] Failed: {e}")
_scimago_cache = {}
return _scimago_cache
def lookup_any(issns: list) -> Optional[dict]:
index = load_scimago()
for issn in issns:
normed = norm_issn(str(issn)) if issn else None
if normed and normed in index:
result = dict(index[normed])
result["matched_issn"] = normed
return result
return None
# ── Subject search index (separate from ISSN index) ──────────────────────
_subject_cache: list = []
def load_subject_index(csv_path: str = None) -> list:
"""Load full journal list for subject browsing β€” separate from ISSN lookup."""
global _subject_cache
if _subject_cache:
return _subject_cache
if csv_path is None:
csv_path = os.path.join(os.path.dirname(__file__), "scimagojr_2025.csv")
rows = []
seen_titles = set()
try:
with open(csv_path, encoding="latin-1", newline="") as f:
reader = csv.DictReader(f, delimiter=",")
for row in reader:
title = row.get("Title", "").strip()
if not title or title in seen_titles:
continue
seen_titles.add(title)
issn_raw = row.get("Issn", "")
issn_parts = re.split(r"[,;\s]+", issn_raw)
issns = [norm_issn(p) for p in issn_parts if norm_issn(p)]
primary_issn = issns[0] if issns else None
try:
h = int(row.get("H index", "0").strip() or 0)
except Exception:
h = 0
rows.append({
"title": title,
"issn": primary_issn,
"quartile": row.get("SJR Best Quartile", "").strip(),
"h_index": h,
"categories": row.get("Categories", "").strip().lower(),
"areas": row.get("Areas", "").strip().lower(),
"publisher": row.get("Publisher", "").strip(),
"open_access":row.get("Open Access", "No").strip(),
"country": row.get("Country", "").strip(),
"rank": row.get("Rank", "").strip(),
"sourceid": row.get("Sourceid", "").strip(),
})
_subject_cache = rows
print(f"[SCImago] Subject index loaded: {len(rows):,} unique journals.")
except Exception as e:
print(f"[SCImago] Subject index failed: {e}")
_subject_cache = []
return _subject_cache
def search_by_subject(category: str, area: str, top_n: int = 15) -> list:
"""
Search journals by normalised category + area strings from GPT.
Returns top_n sorted by h_index descending.
Tries: exact category match -> partial category -> area match.
"""
rows = load_subject_index()
cat_lower = category.lower().strip() if category else ""
area_lower = area.lower().strip() if area else ""
def score(r):
cats = r["categories"]
areas = r["areas"]
if cat_lower and cat_lower in cats:
return 3
if cat_lower:
words = [w for w in cat_lower.split() if len(w) > 3]
if any(w in cats for w in words):
return 2
if area_lower and area_lower in areas:
return 1
if area_lower:
words = [w for w in area_lower.split() if len(w) > 3]
if any(w in areas for w in words):
return 1
return 0
scored = [(score(r), r) for r in rows]
matched = [(s, r) for s, r in scored if s > 0]
if not matched:
return []
matched.sort(key=lambda x: (-x[0], -x[1]["h_index"]))
return [r for _, r in matched[:top_n]]
def get_all_areas() -> list:
"""Return sorted unique area names for frontend hints."""
rows = load_subject_index()
areas = set()
for r in rows:
for a in r["areas"].split(";"):
a = a.strip().title()
if a:
areas.add(a)
return sorted(areas)