SPB Git

spb/wp35_uqo

Public
4.1 KB · 93 lines python
1"""Verify reference candidates against Crossref (works API) and write research/prior_art.csv.23For each candidate: query.bibliographic = "<first_author> <year> <title>", rows=4; a match requires4(i) the first-author family name appears among the returned authors, (ii) |year - candidate year| <= 1,5(iii) normalized title similarity >= 0.80 (difflib). Books/chapters without a Crossref DOI are reported as6UNVERIFIED_CROSSREF and must be verified by other means (publisher/WorldCat) and flagged in the paper.7"""8from __future__ import annotations9import csv, difflib, json, re, sys, time, unicodedata10from pathlib import Path11import requests1213ROOT = Path(__file__).resolve().parents[3]14CAND = ROOT / "research" / "reference_candidates.csv"15OUT = ROOT / "research" / "prior_art.csv"16RAWJ = ROOT / "research" / "crossref_raw.json"17UA = "WP35-UQO-bib-verify/0.1 (mailto:simon-pierre.boucher@uqo.ca)"181920def norm(s: str) -> str:21    s = unicodedata.normalize("NFKD", s or "").encode("ascii", "ignore").decode()22    return re.sub(r"[^a-z0-9 ]+", " ", s.lower()).strip()232425def sim(a: str, b: str) -> float:26    return difflib.SequenceMatcher(None, norm(a), norm(b)).ratio()272829def crossref(q: str):30    r = requests.get("https://api.crossref.org/works", params={"query.bibliographic": q, "rows": 4},31                     headers={"User-Agent": UA}, timeout=60)32    r.raise_for_status()33    return r.json()["message"]["items"]343536def year_of(it):37    for k in ("published-print", "published-online", "issued", "created"):38        d = it.get(k, {}).get("date-parts", [[None]])[0][0]39        if d:40            return d41    return None424344def main():45    rows = [r for r in csv.DictReader(open(CAND), delimiter="|") if r["type"] != "skip"]46    results, raw = [], {}47    for r in rows:48        q = f'{r["first_author"]} {r["year"]} {r["title"]}'49        try:50            items = crossref(q)51        except Exception as e:52            results.append({**r, "status": f"CROSSREF_ERROR {e}", "doi": "", "cr_title": "", "cr_year": "", "cr_container": "", "title_sim": ""})53            continue54        raw[r["key"]] = items55        best = None56        for it in items:57            t = (it.get("title") or [""])[0]58            fam = [norm(a.get("family", "")) for a in it.get("author", [])] + [norm(a.get("name", "")) for a in it.get("author", []) if "name" in a]59            if "editor" in it:60                fam += [norm(a.get("family", "")) for a in it["editor"]]61            y = year_of(it)62            s = sim(r["title"], t)63            if r["type"] in ("book",) and it.get("type") in ("book", "monograph", "edited-book", "reference-book") and s < 0.8:64                s = max(s, sim(r["title"].split(":")[0], t.split(":")[0]))65            ok_auth = any(norm(r["first_author"]) in f for f in fam) if fam else False66            ok_year = y is not None and abs(int(y) - int(r["year"])) <= 167            score = s + 0.1 * ok_auth + 0.1 * ok_year68            if best is None or score > best[0]:69                best = (score, it, t, y, s, ok_auth, ok_year)70        if best and best[4] >= 0.80 and best[5] and best[6]:71            it = best[1]72            status = "VERIFIED_CROSSREF"73        elif best and best[4] >= 0.80 and best[6] and not best[5]:74            it = best[1]; status = "TITLE_YEAR_MATCH_AUTHOR_MISSING"75        else:76            it = best[1] if best else {}77            status = "UNVERIFIED_CROSSREF"78        results.append({**r, "status": status, "doi": it.get("DOI", ""), "cr_title": (it.get("title") or [""])[0],79                        "cr_year": year_of(it) if it else "", "cr_container": (it.get("container-title") or [""])[0] if it else "",80                        "title_sim": round(best[4], 3) if best else ""})81        print(f'{r["key"]:28s} {status:32s} sim={results[-1]["title_sim"]} doi={it.get("DOI","")}')82        time.sleep(0.25)83    with open(OUT, "w", newline="") as f:84        w = csv.DictWriter(f, fieldnames=list(results[0].keys()), delimiter="|")85        w.writeheader(); w.writerows(results)86    RAWJ.write_text(json.dumps(raw, indent=1))87    n_ok = sum(r["status"] == "VERIFIED_CROSSREF" for r in results)88    print(f"\n{n_ok}/{len(results)} VERIFIED_CROSSREF -> {OUT}")899091if __name__ == "__main__":92    main()93
Files · d0c763c57505669cbe819b32e8ff37f71cf2ea3d
Menu

Theme