← 返回 Skill 列表
extension
分类: AI Agent 能力API Key 暂未确认

pubmed-verifier

PubMed引文批量核验:PMID存在性检查、"真PMID指向不同论文"的幻觉引用检测、撤稿识别、Crossref DOI交叉核对,输出CSV/JSON/HTML报告,全程本地运行。 Batch-verify PMID citations against PubMed and catch the hallucination that existence checks miss — a REAL PMID pointing to a DIFFERENT paper (the most common AI-fabricated citation). Five-state citation verification (correct / mismatch / partial / invalid / unknown), citation-context parsing, dual fuzzy matching, Crossref DOI cross-check, retraction detection (RETRACTED papers capped at partial), correct-PMID suggestion, SQLite cache, CSV/JSON claims, HTML/JSON/text reports. Dual data sources with automatic Europe PMC fallback, optional NCBI API key (faster batches), Crossref polite pool, Retry-After backoff, UA rotation, host circuit breaker. Network failures are honestly reported as unverified, never as "not found". Zero dependencies, runs fully local.

person作者: Docsorhubgithub

PubMed Citation Verifier v4.1.0

Batch verification of PMID citations via the PubMed E-utilities API. Not just "does this PMID exist" — does this PMID point to the paper you claim? Zero dependencies, pure standard library, fully local.

When to use this skill

Invoke it whenever citation truth matters:

  • "Verify / check these PMIDs / this reference list" (before submission or release)
  • Auditing citations in AI-generated text (fabricated or mismatched references)
  • Spot-checking a systematic review's bibliography
  • "Does PMID 12345678 really say X?" — point-of-doubt verification
  • Batch QA of a knowledge base's PMID/DOI citations
  • Automatic invocation: trigger this skill whenever the user asks to "verify/check/audit" citations, PMIDs, DOIs, arXiv preprints or references — no flags needed for a first pass; add --claims-file when they supply the expected titles.

Trigger priority & tool choice — explicit "verify / check / audit citations, references, PMIDs, DOIs" requests invoke this skill first. cite-holmes is for deep research with machine-verified citations; when a request mixes research and verification, run the research first, then this tool for the final reference audit.

The five-state verdict

| Verdict | Meaning | |---------|---------| | ✅ Correct | PMID exists AND matches the claimed paper | | ⚠️ Mismatch | PMID exists but points to a different paper (the most common AI hallucination!) | | 🔶 Partial | Some metadata matches (e.g. author+journal but title differs) | | ❌ Invalid | PMID does not exist in PubMed | | ❓ Unknown | Not enough claimed metadata to cross-check — or both data sources unreachable (never misreported as invalid) |

Why existence checks are not enough: a large share of fabricated citations use REAL PMIDs that point to a different paper from the same year/journal/field — in one of our own audits, 4 of 5 "valid" PMIDs were wrong this way. A binary exists/not-exists check misses them all.

Quick start

# Scan a project directory for PMIDs (parses citation context automatically)
python3 scripts/verify_pmids.py --source /path/to/project --output report.html

# Verify specific PMIDs
python3 scripts/verify_pmids.py --pmids 31018962,22213727

# Mismatch demo: PMID 34078778 is actually a dental-materials paper, so the
# JIA claims below will NOT match it — expect ⚠️ mismatch verdicts
python3 scripts/verify_pmids.py --claims '[{"pmid":"34078778","title":"JIA pathogenesis","authors":["Zaripova"],"journal":"Pediatr Rheumatol Online J","year":"2021"}]' --output report.html

# Claims from a CSV file + suggest correct PMIDs for mismatches
python3 scripts/verify_pmids.py --claims-file claims.csv --suggest --output report.html

# Crossref DOI cross-verification + audit working-paper + BibTeX + full pipeline
python3 scripts/verify_pmids.py --source /path/to/files --verify-doi --suggest --output report.html --export-audit audit.json --export-bibtex refs.bib

# Verify DOIs directly (no PMIDs) + delta audit vs a previous run
python3 scripts/verify_pmids.py --dois "10.1038/nature12968,10.4012/dmj.2020-408" --workers 4 --export-audit audit.json --export-csv table.csv
python3 scripts/verify_pmids.py --source /path/to/project --diff audit.json --output report.html

# Verify arXiv IDs (preprints) — mixed audits supported
python3 scripts/verify_pmids.py --arxivs "2401.12345,cs/0211004" --no-cache

# Pre-flight: are the five data sources reachable right now?
python3 scripts/verify_pmids.py --check-net

# Audit a BibTeX or RIS bibliography file directly (PMID > DOI > arXiv routing)
python3 scripts/verify_pmids.py --bibliography refs.bib --no-cache
python3 scripts/verify_pmids.py --bibliography refs.ris --no-cache --export-ris verified.ris

# Verify a reference list copied from a paper draft (inline IDs route
# exactly; title-only entries resolve via PubMed title search)
python3 scripts/verify_pmids.py --parse-text references.txt --no-cache

# Format the verified entries as a ready-to-paste reference list
# (--citation-style: gbt | vancouver | apa | ama; default gbt)
python3 scripts/verify_pmids.py --parse-text references.txt --format-references refs_gbt.txt --citation-style gbt

# Institutional niceties (recommended): NCBI API key + contact email
python3 scripts/verify_pmids.py --source . --verify-doi --ncbi-api-key $NCBI_API_KEY --mailto you@lab.org

What the output looks like

Terminal text report (verdict line, then per-citation evidence):

Readiness: NOT SUBMISSION-READY — 1 invalid, 1 mismatched
Results: 1/3 correct, 1 mismatch, 1 invalid, 0 partial, 0 unknown
============================================================

✅ PMID 31018962 (claims) [correct]
   Actual: Classification criteria for autoinflammatory recurrent fevers
   Claimed: Classification criteria for autoinflammatory recurrent fevers
   Evidence: title ✓ · author ✓ · journal ✓ · year ✓

⚠️ PMID 34078778 (claims) [mismatch]
   Actual: Effect of CAD/CAM materials on the marginal fit of crowds
   Claimed: JIA pathogenesis and treatment
   → Suggest: PMID 34425842 - Juvenile idiopathic arthritis: from aetio...
   Evidence: title ✗ · author ✗ · journal ✗ · year —

❌ PMID 99999999 (cli) [invalid]
   Error: PMID not found in API response

--output report.json carries the same verdicts plus the full evidence chain per citation (evidence.title_match / author_match / journal_match / year_match, fields tri-state, meta_source naming which registry answered, resolved_by when the title-search/OpenAlex legs matched) and a stats block with the submission-readiness verdict. --format-references writes the ready-to-paste citation list shown in the v4.0.0 section.

FAQ & common mistakes

Top 10 things NOT to do (each is detailed below or in Anti-patterns):

| # | Don't | Do instead | |---|-------|------------| | 1 | Treat --pmids existence output as "verified" | Feed --claims-file with titles for real verification | | 2 | Submit claims without title | Always include titles — the verdict caps at partial without one | | 3 | Trust cached verdicts on publication day | Final check with --no-cache | | 4 | Read "not found" as "fabricated" for auto-extracted DOIs | Check doi.org / arxiv.org by hand first | | 5 | Treat the leading ' in CSV cells as corruption | It is the formula-injection guard — strip after import | | 6 | Read the READY line as a quality score | It means "no problems among the checks that ran" | | 7 | Pass --source together with --pmids | --source is ignored entirely when --pmids is given | | 8 | Deep-verify (--verify-doi / --suggest) a thousand-entry sweep | Sweep first, deep-verify the flagged subset | | 9 | Expect author matching across CJK↔Latin names | They are skipped honestly (author_check: skipped) | | 10 | Ship a reference list without the audit trail | --export-audit writes a replayable working paper |

Large batch (hundreds of PMIDs) is slow — how to speed it up? Metadata-only verification queries in batches of 50 with 0.4 s spacing (0.12 s with --ncbi-api-key); cached re-runs are ~5 s. --verify-doi adds one Crossref call per citation and --suggest adds one search per mismatch — skip them for bulk sweeps, run them on the flagged subset.

When must I use --claims-file instead of scanning? Context parsing is heuristic (v3.3.0 guards common abbreviations, exotic formatting can still mis-split). For precise verification — or DOIs in claims (splice detection needs doi) — feed structured JSON/CSV claims.

My citation text is in Chinese — the scan parses little? GB/T 7714-style references (……标题[J]. 刊名, 年… PMID: xxx) parse since v3.9.0 — title/journal comparisons against Latin registries are skipped cross-language, so verdicts rest on year/DOI evidence. Free Chinese prose without a PMID marker still yields little — feed --claims-file for full verdicts regardless of language.

Slow or unstable network (China)? Standard HTTPS_PROXY/HTTP_PROXY env vars are honored natively; raise --timeout; --meta-source europepmc routes via Europe PMC when NCBI is unreachable (per-entry meta_source shows which was used); cached results are reused for 30 days.

❓ unknown vs ❌ invalid? unknown (exit 2) = "could not verify, sources unreachable" — retry later; invalid (exit 1) = "verified not-found". Network failures are never reported as not-found and never cached.

What does RETRACTED mean in a report? The registry itself lists the paper's publication type as "Retracted Publication" (checked for every citation since v2.7.0 — no DOI or flags needed), and/or Crossref records a retraction. The verdict is capped at partial and a human review note is attached — citing it would propagate withdrawn science. A retraction notice is never flagged; papers under Expression of Concern (an editorial note, not a retraction) are not flagged either. Retraction status reflects the registry at cache time — for a final pre-submission check, run with --no-cache.

Mismatch reported but the title looks similar? Check details for which field diverged; thresholds are strict on purpose. Feed the full citation via --claims-file for a precise verdict.

Can I audit my .bib or .ris file directly? Yes — --bibliography refs.bib (BibTeX) and --bibliography refs.ris (RIS/Zotero/EndNote/Mendeley) route each entry by PMID > DOI > arXiv and cross-check the claimed metadata. --lint-claims validates either format offline first. The round trip works both ways: --export-bibtex and --export-ris output can be fed back after edits.

My claims file seems to lose rows / verdicts look weaker than expected? Lint it offline first: python3 scripts/verify_pmids.py --lint-claims claims.csv reports unusable rows, ID shape errors, unknown columns (typo'd headers like "titel"), missing titles and DOI prefix problems — no network, exit 1 on errors.

Can I verify a DOI with claimed metadata (full verdict)? Yes — since v3.4.0 a claims row keyed by doi (with title, optionally authors/journal/year) gets the same cross-check as PMID claims: correct / mismatch / partial against the registered metadata. A DOI row without claims stays unknown (existence only).

Why did my arXiv citation drop from correct to partial? Your claims row paired an arxiv_id with a doi, and the DOI does not match the version-of-record DOI registered on that arXiv entry — a typo, or a DOI from a different paper. The registered DOI is in details; fix the claim or drop the doi cell.

Worked scenarios

  • "The reviewer asked how I checked my references." — run the audit with --export-audit audit.json --verify-doi: the JSON working-paper contains tool identity, version, the exact (API-key-redacted) invocation, and the per-citation evidence chain a reviewer or editor can replay. Submit it alongside the manuscript.
  • "Checking the reference list of my thesis." — copy the list into a plain-text file (one reference per line, or keep the [1] numbering) and run --parse-text refs.txt: inline PMIDs/DOIs route exactly; English entries resolve via PubMed title search; Chinese entries resolve via the OpenAlex CJK leg. Finish with --format-references refs_clean.txt --citation-style gbt for a corrected, uniformly formatted list.
  • "Screening a systematic review's bibliography (PRISMA)." — verify the full list once, export the audit as the baseline, then re-run --diff audit.json before each resubmission: newly retracted, degraded and new citations are reported as deltas, so you only re-read what changed.

Anti-patterns — things done WRONG

Each entry: the mistake → why it fails → the right way.

  1. Treating --pmids output as "fully verified" — existence-only. → Wrong: "all 5 PMIDs exist, so the citations are correct." → Right: existence-checked only; feed --claims-file with titles for real verification (the READY line says so explicitly).
  2. Claims without title — author/journal/year alone can never reach correct; the report caps at partial. → Always include titles.
  3. Trusting a cached verdict right after publication day — a brand-new PMID may have been cached as not-found by an earlier run, and retraction status is as of cache time. → Final pre-submission check: --no-cache.
  4. Assuming "not found" always means fabricated — a Crossref 404 is re-checked at OpenAlex (DataCite/Zenodo/Chinese-registry DOIs live there), so "unknown in both registries" is now the bar; auto-extracted DOIs that 404 in both stay suspects. arXiv IDs removed by moderators also return empty. → Check doi.org / arxiv.org by hand before accusing.
  5. Copying the leading ' from CSV cells — that apostrophe is the formula-injection guard, not data corruption. → Strip it after import.
  6. Reading the READY line as a quality score — it only means "no problems found among the checks that ran", not "this paper is good".

Boundaries — declared limits

What this tool can NOT do, consolidated in one place:

  • Splice/mismatch signals report disagreement, never pick a side — when claim and registry disagree, a human reads the evidence line.
  • Cross-language authors are skipped, not failed — CJK↔Latin author names are never compared (author_check: skipped); the verdict rests on title/journal/year alone.
  • Context parsing is heuristic — v3.3.0 keeps common abbreviations (U.S., e.g., vs., St., Vol., No.) from splitting a title, but exotic formatting can still mis-split; for exact metadata use --claims-file.
  • DataCite/repository DOIs are not in Crossref — OpenAlex covers them (v4.1.0): a Crossref 404 now gets a second look at OpenAlex; only when BOTH registries report the DOI unknown do the 404 semantics apply (--dois/claims = invalid, auto-extracted = suspect). Resolved-via- OpenAlex entries carry meta_source: openalex.
  • arXiv moderator removals also return "not found" — the invalid verdict carries that caveat in its details.
  • Retraction status is as-of-cache-time — final pre-submission checks should run with --no-cache.
  • Single-letter initials never match — "Smith J" vs "Smith John" is not counted as a miss.
  • unknown ≠ invalid — unreachable sources yield exit 2 and unknown; network failures are never reported as "not found" and never cached.
  • arXiv pacing is deliberate — the official API asks for ≥3 s between calls; large arXiv batches are slow by design (progress + ETA on stderr). Entries that register a version-of-record DOI add one Europe PMC lookup each for the PMID link.
  • DOI claims compare against the registry that actually answered — the linked PubMed record when the DOI resolves to one, Crossref otherwise (Crossref author fields are sparser, so the author mark is more often "—"); a DOI row without a claimed title stays unknown, not partial; an explicitly user-provided DOI (--dois or claims) that is missing from Crossref counts as invalid.

Claims reference format

--claims-file accepts JSON (an array of objects) or CSV. Recognized columns: pmid, title, authors (semicolon/pipe-separated), journal, year, doi, arxiv_id. A row needs one of pmid, arxiv_id or doi; a missing title caps the verdict at partial.

pmid,title,authors,journal,year,doi,arxiv_id
31018962,Candidate criteria for diagnosis of familial...,Gattorno,Ann Rheum Dis,2019,10.1136/annrheumdis-2019-215048,
,Attention Is All You Need,Vaswani,NeurIPS,2017,,1706.03762
,City size and the spreading of COVID-19 in Brazil,Silva Junior;Other,PLOS ONE,2020,10.1371/journal.pone.0239699,

Validate any file offline first: --lint-claims file.csv reports unusable rows, ID shape errors, unknown columns, duplicates and missing titles (no network). Lint wins when combined with verification flags — only the lint runs.

Best practices & tuning

  • Speed up large batches — request an NCBI API key (see https://ncbiinsights.ncbi.nlm.nih.gov/api-keys/): batches of 50 IDs run at 0.12 s spacing instead of 0.4 s; cached re-runs take seconds.
  • Parallel DOI verification — --workers (default 4, cap 8) applies to Crossref resolution and Europe PMC linking; arXiv stays serial by official etiquette (≥3 s between calls).
  • Two-phase workflow — sweep with metadata-only verification first (no --verify-doi, no --suggest), then deep-verify only the flagged subset; each deep flag adds one API call per citation.
  • Round-trip bibliographies — --export-bibtex / --export-ris write verified entries; after edits, --bibliography refs.bib / refs.ris re-audits the file (the PMID re-links the full record).
  • Claims over context parsing — whenever you know the expected titles, feed --claims-file: it enables the full verdict ladder and the DOI / arXiv pairing checks. Validate the file offline first: python3 scripts/verify_pmids.py --lint-claims claims.csv reports ID shape errors, missing titles, unknown columns and duplicates without any network access.
  • Flaky networks — raise --timeout; HTTPS_PROXY/HTTP_PROXY are honored natively; unreachable NCBI falls back to Europe PMC automatically (meta_source shows which answered).
  • Cache policy — results cache 30 days, negative entries 3 days; --cache-days to tune; --no-cache for the final pre-submission pass.
  • Scale expectations — metadata-only throughput is API-bound (~1–2 min per 1000 PMIDs with an API key); DOI resolution adds one Crossref call per DOI. One deliberate trade-off: the verifier is a single stdlib-only file — copy scripts/verify_pmids.py anywhere with Python 3.9+ and it runs, no pip, no venv (that portability is why the code is not split into modules).

v2.2.0 — network hardening

| Feature | Flag | Effect | |---------|------|--------| | NCBI API key | --ncbi-api-key / env NCBI_API_KEY | Rate ceiling 3→10 req/s, batch interval 0.4s→0.12s (~3x faster) | | Europe PMC fallback | --meta-source auto\|ncbi\|europepmc | NCBI batch failure automatically retries via Europe PMC (free, no key); per-entry origin in JSON (meta_source) | | Crossref polite pool | --mailto / env PUBMED_VERIFIER_MAILTO | ?mailto= on Crossref + tool/email params on NCBI — more generous limits | | Retry-After backoff | automatic | 429 responses honored (clamped 1–5 s) instead of failing | | UA rotation | automatic | 403/406 retried with a browser User-Agent | | Host circuit breaker | automatic | After 2 call-level transport failures a host is skipped with an actionable message; success resets; HTTP errors never trip it | | Honest unknown | automatic | Network failures report as ❓ unknown + exit code 2, never as "PMID not found", and are never cached |

Exit codes: 0 clean · 1 problems found (invalid / mismatch / retracted / DOI-splice, incl. arXiv claimed-DOI pairing mismatches) · 2 could not verify (data sources unreachable) — automation can tell "all good" from "no answer".

v2.3.0 — retraction detection

With --verify-doi, each cited DOI is also checked against Crossref's withdrawal records (updated-by). A paper Crossref lists as RETRACTED is:

  • flagged in JSON (retracted: true + retraction_note) and in reports,
  • capped at 🔶 partial even when every metadata field matches — citing a retracted paper is never "correct"; the report says human review required.

Corrections and other update types do not trigger the cap. Crossref outages never flag anything (a missing check is not a retraction).

v2.4.0 — DOI↔PMID cross-check & journal abbreviations

  • DOI splice detection: add a doi field to your claims (JSON or CSV). The claimed DOI is compared with the DOI registered for that PMID — a mismatch is a splice/fabrication signal (a real DOI attached to the wrong paper): flagged in JSON (doi_splice_suspect), verdict capped at 🔶 partial, counted in exit 1. Uses the PubMed record only — no extra API call.
  • Journal abbreviation equivalence: journal matching now understands NLM-style abbreviations in both directions — "N Engl J Med" matches "New England Journal of Medicine", "Pediatr Rheumatol" matches "Pediatric Rheumatology" (in-order word prefixes, function words skipped). No more false "journal differs" for abbreviated citations.

Known limits: highly ambiguous abbreviations can over-match at the journal-only level ("J Immunol" ~ "Journal of Immunology Research") — the title remains the decisive field. A DOI-splice flag can also appear on an otherwise-unverifiable citation (the DOI mismatch is an independent fact).

v2.5.0 — author-name verification

  • Initials never match: single-letter tokens ("A.", "L.") on either side are excluded from surname matching — an initial is not evidence, and substring-matching one produced false author hits.
  • Cross-language honesty: CJK author names claimed against Latin registry records (or the reverse) are skipped, not counted as a mismatch — the report marks them author_check: skipped and the verdict falls back to what was actually comparable (title/journal/year), or to ❓ unknown when nothing else is checkable.

Security & behavior declaration

  • Single-run CLI: scan, verify, write the report, exit. No daemons, no background jobs, nothing downloaded or installed at runtime (pure standard library, zero dependencies).
  • Network access is limited to these official academic registries, always over HTTPS: eutils.ncbi.nlm.nih.gov, www.ebi.ac.uk (Europe PMC), api.crossref.org, api.openalex.org, export.arxiv.org — no other hosts are contacted; no telemetry, no analytics, no data collection — the only outbound payloads are the PMIDs, DOIs and titles you asked to verify.
  • Your files and reports stay on your machine. Writes are limited to the report paths you pass and the SQLite cache under ~/.cache/pubmed-verifier/ (--no-cache to disable).
  • Optional environment variables NCBI_API_KEY / PUBMED_VERIFIER_MAILTO authenticate or attribute your own API requests and are never sent anywhere else.
  • No OS integration: no subprocesses, no system services, no privilege changes, no scheduled tasks.

v2.6.0 — audit working-paper & report v2

  • --export-audit audit.json — a self-contained JSON working-paper for transparent review: tool identity and version, the exact (API-key-redacted) invocation, per-citation evidence chains (claimed vs registered fields, title match scores from both algorithms, author match with cross-language skip records, DOI cross-check, retraction signals) and the verdict-ladder trace for every citation. A reviewer can replay the entire verification from this file alone.
  • HTML report v2 — verdict filter tabs, severity-sorted rows (retracted and DOI-splice first, highlighted), a field-level evidence column (title/author/journal/year ✓✗—) and a reproducibility footer (redacted command line + version + data sources).
  • Reliability — negative cache entries now expire after 3 days (a legitimately new, ahead-of-print PMID is no longer reported "not found" for a month), and the circuit breaker self-heals: after a 30 s cooldown it admits one probe call and resets on success.

v2.7.0 — retraction for every PMID, BibTeX export, readiness verdict

  • Retraction detection, source-independent — the registry's own publication type ("Retracted Publication"; present in both NCBI esummary and Europe PMC) now flags retracted papers for EVERY citation: no DOI required, no --verify-doi required, and the flag survives the cache (schema v3). Crossref updated-by remains the detail source (the retraction-notice DOI) when --verify-doi is on. A retraction notice itself is never flagged.
  • --export-bibtex refs.bib — export the verified bibliography: correct entries as @article, partial entries commented out with their divergence note, mismatched/invalid/unknown/retracted entries excluded and counted.
  • Submission-readiness verdict — every report now leads with one line: SUBMISSION READY or NOT SUBMISSION-READY — <per-problem counts>.
  • Cache schema v3 (adds a retracted column, auto-migrated).

v2.8.0 — DOI-native verification & delta audits

  • --dois "10.x/a, 10.y/b" — verify DOIs natively, no PMID required. --source scans now also extract DOIs from your files automatically. Each DOI is resolved via the Crossref works API: not-found on an explicitly provided DOI = fabrication signal (invalid, exit 1); on one auto-extracted from scanned text it stays a suspect (unknown) — scanned strings are never user-endorsed, and DataCite/repository DOIs do not live in Crossref, so always double-check at doi.org. Resolved = existence confirmed with the registered metadata attached for manual comparison — existence is never dressed up as a match.
  • --diff previous-audit.json — delta audit against a previous working paper: newly retracted (the safety signal — a paper retracted after your last audit; act on it: swap or drop the citation, cite the retraction notice instead, and re-check any conclusion that relied on it), degraded, improved, new and dropped citations, with counts in every report format. Built for periodic knowledge-base audits: "what changed since last time?"

v2.9.0 — DOI entries become first-class

  • DOI→PMID linking — a resolved DOI is linked back to its PMID via the Europe PMC DOI field query, pulling the full PubMed record: complete metadata, retraction pubtype signal, and cache coverage. A DOI citation now gets the same five-state record as a PMID citation (existence confirmation only — the verdict remains unknown until claims are provided).
  • Parallel DOI resolution — --workers N (default 4, max 8) resolves DOI batches on a thread pool (roughly 3x faster on large lists), with live progress output. For large --dois batches, set --mailto to stay in Crossref's polite pool.
  • --export-csv table.csv — spreadsheet-friendly audit table (key/verdict/flags/fields/details; formula-injection hardened).

v3.0.0 — arXiv ID verification (three citation types, one audit)

Reference lists carry preprints. v3.0.0 verifies arXiv IDs alongside PMIDs and DOIs: arXiv:2401.12345 and arxiv.org/abs/... patterns are extracted from scans (or passed via --arxivs), checked against the official arXiv API, and judged — nonexistent ID = fabrication signal (invalid, exit 1); resolving ID = registered title/year attached, verdict stays unknown. Malformed IDs (bad YYMM month) are flagged by shape. Timely: arXiv penalizes submissions containing hallucinated or unverified references (2026-05 policy) — audit before you submit.

v3.3.0 — preprint ↔ published-version cross-check

arXiv entries carry the version-of-record DOI their authors registered at publication (arxiv:doi). v3.3.0 puts it to work:

  • Claimed DOI vs registered DOI — a claims row with both arxiv_id and doi is cross-checked: agreement is reported as evidence (fields.doi ✓); disagreement caps the verdict at partial — the DOI belongs to a different paper (same failure class as PMID DOI-splice).
  • Version of record surfaced — verifying a bare preprint ID now shows the registered DOI and, when the published version is PubMed-indexed, its linked PMID — cite and verify the final version, not just the preprint.
  • Honest accounting — the readiness line counts arXiv DOI-pairing mismatches as problems; DOI/arXiv phase progress (stderr) now includes elapsed time and an ETA for large batches.
  • Context parsing no longer truncates titles at sentence-internal abbreviations ("U.S. population", "e.g.", "vs.", "Vol.").

Pairing example (match → correct with DOI evidence; wrong DOI → partial):

python3 scripts/verify_pmids.py --claims '[{"arxiv_id":"2005.13892",
  "title":"City size and the spreading of COVID-19 in Brazil",
  "doi":"10.1371/journal.pone.0239699"}]'

v3.4.0 — DOI claims become first-class

Claims rows could carry a PMID or an arXiv ID — a row keyed by DOI alone was silently ignored, and DOI entries always stayed unknown ("no claimed metadata to cross-verify"). v3.4.0 closes the matrix: all three citation types now accept claimed metadata.

  • A claims row with a doi (no PMID, no arXiv ID) is cross-checked against the registered metadata — the linked PubMed record when the DOI resolves to one, Crossref otherwise — and gets the full verdict ladder: correct / mismatch / partial.
  • Retraction capping applies as everywhere: a claimed-correct match on a retracted paper is capped at partial with the retraction note.
  • Without claims, DOI entries stay unknown — existence is never dressed up as a match.
python3 scripts/verify_pmids.py --claims '[{"doi":"10.1371/journal.pone.0239699",
  "title":"City size and the spreading of COVID-19 in Brazil",
  "journal":"PLoS ONE","year":"2020"}]'

v3.5.0 — claims lint & usage-first restructuring

  • --lint-claims FILE — offline pre-flight for claims files (JSON/CSV, zero network): ID shape errors, missing titles (the verdict would cap at partial), unknown/typo'd columns, DOI prefix checks, unusable rows — exit 1 on errors. Fix the format before the run instead of guessing from weak verdicts.
  • Documentation restructured around usage: FAQ, anti-patterns and declared boundaries now sit right after Quick start, led by a Top-10 "don't do this" table; new Best practices & tuning section (API-key batching, worker tuning, two-phase deep-verification, cache policy, scale expectations — and why the verifier is deliberately one file).

v3.6.0 — BibTeX bibliography audit

--bibliography refs.bib audits a .bib file directly: entries route by PMID (the pmid field, or a "PMID: NNNN" in the note — including the notes --export-bibtex itself writes) > DOI > arXiv (eprint, or an "arXiv:XXXX.XXXXX" in the journal/note), each carrying its claimed title/authors/journal/year for the full cross-check. Entries without any routable ID are counted and skipped. This closes the loop with --export-bibtex: a verified bibliography can be re-audited after edits. --lint-claims refs.bib validates the file offline (unroutable entries, missing titles, unclosed blocks).

v3.7.0 — RIS bibliography support

--bibliography now accepts RIS files (refs.ris — Zotero/EndNote/ Mendeley exports) alongside BibTeX: records route by PMID (AN tag, or a "PMID: NNNN" note) > DOI (DO) > arXiv (UR/eprint), each with claimed metadata for the full cross-check. --export-ris completes the loop — verified entries as TY JOUR, partials as TY DATA with a PARTIAL note. --lint-claims refs.ris validates offline (unroutable records, missing titles, duplicates, unterminated records).

v3.8.0 — verdict-ladder corrections (independent external test audit)

An independent real-data audit (24 scenarios, registry-ground-truthed) found three judgment-ladder defects; all three are fixed and locked:

  • B1 scan false positives — when a citation is not the first sentence of its block, the author line was taken as the claimed title (correct references reported as mismatch). The parser now shifts past an author-shaped segment.
  • B2 author mis-attribution — a claimed author set entirely different from the registry (zero surname overlap) was still reported as correct when title and journal matched; it now caps at partial. Partial surname overlap (abbreviated or reordered names) keeps correct.
  • B3 title-less claims — author+journal+year matches without a claimed title now cap at partial, exactly as the FAQ/anti-patterns/boundaries always promised (the same author/journal/year can cover several papers). Cross-language author skips follow the same cap.
  • Also: duplicate arXiv IDs announce their merge on stderr (run --lint-claims to catch duplicate claim rows offline); the cross-language skip no longer coexists with contradictory details text; empty --claims gets a one-line hint; docs state that Chinese-language citation contexts parse weakly — use --claims-file.

v3.9.0 — Chinese references (GB/T 7714), network pre-flight, Python API

  • GB/T 7714 Chinese references parse — ……标题[J]. 刊名, 年… PMID: xxx contexts now yield the title (via the [J]/[M]/[R] type marker), authors, journal, year and any DOI that follows the PMID. The title comparison against a Latin registry title is skipped cross-language (never a mismatch source); a DOI in the reference is cross-checked against the registry like any claims DOI. Requires a PMID marker per reference.
  • --check-net — probes the data sources (5 s each), shows your proxy state and prints practical next steps when something is unreachable. Run it when results come back unknown on a constrained network.
  • Python API reference — references/python_api.md documents the stable importable surfaces (fetch_summaries, cross_check_citation, verify_doi_entry, parse_citation_context, suggest_correct_pmid) with copy-paste examples.

v4.0.0 — plain-text reference lists & formatted reference list export

  • --parse-text refs.txt — verify a reference list copied straight out of a manuscript draft. Numbered entries ([1], 1.) split cleanly; inline PMID / DOI / arXiv IDs route exactly (full five-state cross-check); a markerless entry with a parseable title resolves via PubMed title search and is marked resolved_by: title_search — weaker evidence than a supplied ID, honestly labeled, and never a mismatch source. A network failure stays unknown with a retry hint, never "not found". Entries with no routable ID and no usable title are skipped with a note instead of being guessed.
  • --format-references out.txt --citation-style gbt|vancouver|apa|ama — the formatting leg of verify-then-format: verified entries rendered as a ready-to-paste numbered reference list (GB/T 7714-2015 numeric for Chinese submissions, Vancouver, APA 7th, AMA 11th). Formatting stands on registry-verified records only: correct entries form the list, partial entries go to a manual-review section (never silently dropped), and retracted entries are excluded with a warning. Author lists are formatted per style without inventing names — a registry-truncated list closes with the style's et-al form, and APA discloses the truncation.
  • Markerless Latin references now parse through the full sentence segmentation (titles feed the title-search leg); CJK entries without a PMID/DOI marker keep the documented weak-parsing boundary — supply a PMID or DOI for exact routing.
  • examples/refs_list.sample.txt ships a ready-made parse-text fixture.

v4.1.0 — OpenAlex fallback & Chinese-reference title search

  • OpenAlex is now the fourth data source — a DOI that Crossref reports 404 gets a second look at OpenAlex before any verdict: DataCite, Zenodo, SSRN and Chinese-registry DOIs (which do not live in Crossref) become fully decidable — resolved, metadata attached, claims cross-checked, retractions flagged (is_retracted). Only when OpenAlex also comes up empty do the original 404 semantics stand (explicit DOI = invalid, scanned DOI = suspect). --check-net probes five sources now.
  • Chinese references without any ID can now be verified — a markerless CJK entry in --parse-text searches OpenAlex by its title (the query is the title-shaped segment of the entry, not the whole line); a candidate counts only when its registered title appears verbatim in your entry text with an agreeing year, and the verdict carries resolved_by: openalex_cjk_search. No hit = honestly unknown; this leg never manufactures a mismatch, and a network failure stays unknown.
  • Docs: the reports' actual shape is now shown in "What the output looks like"; three worked scenarios added to the FAQ (reviewer evidence, thesis citation check, PRISMA screening).

How it works

  1. Extract + parse context — finds PMID: 12345678 / PubMed URLs in .html .md .txt .htm .json, and parses the surrounding reference into claimed authors / title / journal / year.
  2. Fetch metadata (cached) — PubMed esummary in batches of 50, SQLite cache (30 days, --cache-days), 3 retries with backoff. Europe PMC steps in per failed batch when NCBI is unreachable.
  3. Cross-check claimed vs actual — dual fuzzy matching: word-level Jaccard overlap ≥ 50% OR SequenceMatcher ≥ 90% on titles; author surname hits; journal containment or NLM abbreviation equivalence; exact year.
  4. DOI↔PMID cross-check (automatic when claims include doi) — a claimed DOI differing from the PMID's registered DOI is a splice/fabrication signal (capped at partial).
  5. Crossref DOI verification (optional --verify-doi) — resolves each cited DOI via Crossref, compares the registered title with the PubMed record (doi_title_match in JSON), and detects RETRACTED papers (verdict capped at partial). A doi_verified: false with note "crossref unreachable" is a network fact, not a verdict.
  6. Suggest the right PMID (optional --suggest) — for mismatches, searches PubMed with the claimed metadata and proposes top-3 candidates. (Suggestion search always uses NCBI, even with --meta-source europepmc.)

Context parsing is heuristic — since v3.3.0, common abbreviations ("U.S.", "e.g.", "vs.") no longer split a title, but exotic formatting still can. For precise verification, feed structured claims via --claims-file.

| Report | Flag | Use | |--------|------|-----| | HTML | --output report.html | Human review: claimed vs actual side by side | | JSON | --output report.json | Programmatic processing (includes meta_source per entry) | | Text | default | Quick terminal look |

Performance

Measured on a 225-PMID audit (5 esummary batches): metadata-only verification runs in seconds; cached re-runs take ~5 s. Each batch waits 0.4 s between calls (0.12 s with --ncbi-api-key). Optional extras are per-citation: --verify-doi adds one Crossref call (~0.5–1 s) per cited DOI, and --suggest adds one PubMed search per mismatch.

When to use which tool

  • pubmed-verifier (this skill) — fast, batch, targeted: I have a list of PMIDs/DOIs and need to know if they are real and correctly cited.
  • cite-holmes — deep research: interrogate every citation of a whole document across multiple databases, with graded confidence reports.

They share the same five-state philosophy and are safe to use together.

Use cases

  • Systematic review / meta-analysis reference audits
  • Verifying citations in AI-generated content
  • Pre-submission self-check of a manuscript's reference list
  • Medical knowledge base / teaching material QA
  • Pharmacovigilance literature verification

Related tools

Each tool solves one step of reference work; use whichever fits the task.

  • cn-med-oa — free Chinese medical literature full-text download & metadata
  • cite-holmes — deep research with machine-verified citations
  • paper-polisher-pro — academic polishing, terminology & journal precheck
  • academic-figures — publication-ready scientific figures in one command
  • doc-holmes — layout-preserving PDF translation (in testing)

Typical order: get papers (cn-med-oa), verify citations (this tool or cite-holmes), polish (paper-polisher-pro), make figures (academic-figures), translate PDFs (doc-holmes) — pick whichever step you need.

Files

| File | Purpose | |------|---------| | scripts/verify_pmids.py | Main verifier (v4.1.0, stdlib-only) | | references/api_examples.md | PubMed / Europe PMC / Crossref / arXiv API notes | | references/python_api.md | Calling the verifier from Python (stable surfaces + examples) | | examples/claims.sample.csv | Reference format for --claims-file (incl. a DOI-only row) | | examples/refs.sample.bib | Sample bibliography for --bibliography (DOI/PMID/arXiv routing) | | examples/refs.sample.ris | Sample RIS bibliography (Zotero/EndNote/Mendeley) | | tests/ | Offline matrix + real-network acceptance (repo only, not in the package) |

License

MIT-0 — free to use, modify and redistribute, no attribution required.