SPB Git

spb/wp37_uqo

Public
3.3 KB · 60 lines python
1"""Verify every intended reference against Crossref (public API, polite pool) and build the verified bibliography.23For each seed (author words, title, year) we query Crossref, keep the best candidate only if the title similarity is4high and the year matches (±1), and record DOI, container title, volume, pages. Unverified entries are listed in5literature/unverified.csv and are NOT cited in the paper.6"""7import time, re, json, requests, yaml, pandas as pd8from rapidfuzz import fuzz9from src.common import ROOT, get_logger, ledger1011log = get_logger("crossref")12UA = "WP37-UQO-research/0.1 (mailto:simon-pierre.boucher@uqo.ca)"13LIT = ROOT / "literature"1415def norm(s): return re.sub(r"[^a-z0-9 ]", " ", (s or "").lower())1617def query(author, title, year):18    params = {"query.bibliographic": f"{author} {title} {year}", "rows": 5, "select": "DOI,title,author,issued,container-title,volume,issue,page,type,publisher"}19    r = requests.get("https://api.crossref.org/works", params=params, headers={"User-Agent": UA}, timeout=60)20    r.raise_for_status()21    return r.json()["message"]["items"]2223def main():24    seeds = yaml.safe_load((LIT / "seed_references.yaml").read_text())25    rows = []26    for s in seeds:27        if not s["author"]:28            rows.append({**s, "verified": False, "note": "placeholder topic query, needs a specific paper"}); continue29        try:30            items = query(s["author"], s["title"], s["year"])31        except Exception as e:32            rows.append({**s, "verified": False, "note": f"crossref error {type(e).__name__}"}); continue33        best, best_sim = None, 034        for it in items:35            t = " ".join(it.get("title") or [])36            sim = fuzz.token_set_ratio(norm(t), norm(s["title"])) / 10037            yr = (it.get("issued", {}).get("date-parts") or [[None]])[0][0]38            fam = " ".join(a.get("family", "") for a in it.get("author", []) or [])39            auth_ok = all(norm(w) in norm(fam) for w in s["author"].split()[:1])40            if sim > best_sim and yr and abs(int(yr) - int(s["year"])) <= 1 and auth_ok:41                best, best_sim = it, sim42        if best and best_sim >= 0.90:43            yr = best["issued"]["date-parts"][0][0]44            rows.append({**s, "verified": True, "doi": best["DOI"], "crossref_title": " ".join(best.get("title") or []),45                         "journal": " ".join(best.get("container-title") or []), "volume": best.get("volume"), "issue": best.get("issue"),46                         "pages": best.get("page"), "pub_year": yr, "type": best.get("type"), "publisher": best.get("publisher"),47                         "authors": "; ".join(f"{a.get('family','')}, {a.get('given','')}" for a in best.get("author", []) or []),48                         "title_sim": round(best_sim, 3), "note": ""})49        else:50            rows.append({**s, "verified": False, "note": f"no confident Crossref match (best sim {best_sim:.2f})"})51        time.sleep(0.3)52    df = pd.DataFrame(rows)53    df.to_csv(LIT / "references_verified.csv", index=False)54    df[~df.verified].to_csv(LIT / "unverified.csv", index=False)55    log.info("verified %d / %d ; unverified: %s", df.verified.sum(), len(df), df.loc[~df.verified, "key"].tolist())56    ledger("2", "verify_refs", f"{int(df.verified.sum())}/{len(df)} verified via Crossref")5758if __name__ == "__main__":59    main()60
Files · a1cc09c32a0ecdd892338c87a05a8f63090c6324
Menu

Theme