SPB Git

Repository Public Research preview

90_verify_bib.py 4.5 KB 115 lines python

1#!/usr/bin/env python32"""3================================================================4Auteur  : Simon-Pierre Boucher5Contact : simon-pierre.boucher@uqo.ca6Projet  : Prévision de volatilité réalisée multi-actifs7          (HAR-RV vs GARCH vs Machine Learning)8Fichier : 90_verify_bib.py9Description : Utilitaire de révision — interroge Crossref pour10              chaque entrée de paper/references.bib (titre +11              premier auteur), rapporte le meilleur DOI candidat,12              la similarité de titre et les métadonnées, et écrit13              docs/bib-verification.csv. Aucune modification du14              .bib n'est faite automatiquement.15Usage : python scripts/90_verify_bib.py [--bib paper/references.bib]16================================================================17"""1819from __future__ import annotations2021import argparse22import csv23import difflib24import re25import sys26import time27from pathlib import Path2829import requests3031ROOT = Path(__file__).resolve().parents[1]32API = "https://api.crossref.org/works"33UA = {"User-Agent": "wp12-bib-verifier/1.0 (mailto:simon-pierre.boucher@uqo.ca)"}343536def parse_bib(text: str) -> list[dict[str, str]]:37    """Minimal BibTeX parser (one field per line, as in references.bib)."""38    entries: list[dict[str, str]] = []39    for block in re.split(r"\n(?=@)", text):40        m = re.match(r"@(\w+)\{([^,]+),", block.strip())41        if not m:42            continue43        e = {"type": m.group(1), "key": m.group(2).strip()}44        for fm in re.finditer(r"^\s*(\w+)\s*=\s*\{(.*)\},?\s*$", block, re.M):45            e[fm.group(1).lower()] = fm.group(2).strip()46        entries.append(e)47    return entries484950def clean(s: str) -> str:51    s = re.sub(r"[{}\\]", "", s)52    s = re.sub(r"\s+", " ", s)53    return s.strip().lower()545556def main() -> None:57    ap = argparse.ArgumentParser()58    ap.add_argument("--bib", default=str(ROOT / "paper" / "references.bib"))59    ap.add_argument("--out", default=str(ROOT / "docs" / "bib-verification.csv"))60    args = ap.parse_args()61    entries = parse_bib(Path(args.bib).read_text(encoding="utf-8"))62    rows = []63    for e in entries:64        title = clean(e.get("title", ""))65        first_author = clean(e.get("author", "").split(" and ")[0])66        q = {"query.bibliographic": f"{e.get('title','')} {first_author}", "rows": 3}67        try:68            r = requests.get(API, params=q, headers=UA, timeout=30)69            items = r.json()["message"]["items"]70        except Exception as exc:  # noqa: BLE00171            rows.append({**{k: e.get(k, "") for k in ("key", "year", "doi")},72                         "status": f"ERROR {exc}"})73            print(e["key"], "ERROR", exc, file=sys.stderr)74            continue75        best, best_sim = None, 0.076        for it in items:77            t = clean(" ".join(it.get("title", [])))78            sim = difflib.SequenceMatcher(None, title, t).ratio()79            if sim > best_sim:80                best, best_sim = it, sim81        row = {"key": e["key"], "year": e.get("year", ""), "doi_in_bib": e.get("doi", "")}82        if best is None:83            row.update(status="NO MATCH")84        else:85            cy = ""86            for k in ("published-print", "published-online", "issued"):87                parts = best.get(k, {}).get("date-parts", [[None]])88                if parts and parts[0] and parts[0][0]:89                    cy = str(parts[0][0]); break90            row.update(91                status="OK" if best_sim >= 0.85 else "CHECK",92                sim=f"{best_sim:.2f}",93                crossref_doi=best.get("DOI", ""),94                crossref_title=" ".join(best.get("title", []))[:120],95                crossref_container=" ".join(best.get("container-title", []))[:60],96                crossref_year=cy, crossref_volume=best.get("volume", ""),97                crossref_issue=best.get("issue", ""), crossref_pages=best.get("page", ""),98            )99        rows.append(row)100        print(f"{row['key']:32s} {row['status']:8s} {row.get('sim','')} {row.get('crossref_doi','')}")101        time.sleep(0.3)102    fields = ["key", "year", "doi_in_bib", "status", "sim", "crossref_doi", "crossref_title",103              "crossref_container", "crossref_year", "crossref_volume", "crossref_issue",104              "crossref_pages"]105    Path(args.out).parent.mkdir(parents=True, exist_ok=True)106    with open(args.out, "w", newline="", encoding="utf-8") as fh:107        w = csv.DictWriter(fh, fieldnames=fields, extrasaction="ignore")108        w.writeheader()109        w.writerows(rows)110    print(f"written {args.out} ({len(rows)} entries)")111112113if __name__ == "__main__":114    main()115
Files main
Menu

Theme