Implement Smart PubMed Research Assistant version 2

Added a comprehensive implementation of a Smart PubMed Research Assistant with features for searching, fetching abstracts, and exporting results in various formats.
This commit is contained in:
Daniel Onyejesi 2026-03-04 21:19:56 -05:00 committed by GitHub
parent 9212fd77e3
commit e1d5cdbf72
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

847
version2.py Normal file
View file

@ -0,0 +1,847 @@
"""
title: Smart PubMed Research Assistant
author: Research Assistant
version: 6.2.0
date: 2025-01-01
license: MIT
description: Intelligent PubMed research assistant. Uses PubMed Automatic Term Mapping. Fetches abstracts with embedded citation numbers. Vancouver-style references. RIS export. Works without API key.
"""
import requests
import re
import xml.etree.ElementTree as ET
from typing import List, Dict, Optional, Tuple
from pydantic import BaseModel, Field
class Tools:
class Valves(BaseModel):
ncbi_api_key: str = Field(
default="",
description="Optional: NCBI API key from https://www.ncbi.nlm.nih.gov/account/settings/ — leave empty to work without it."
)
def __init__(self):
self.base_url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils"
self.valves = self.Valves()
self._cache = {}
self._last_results = []
self._last_query = ""
def _api_params(self, params: dict) -> dict:
if self.valves.ncbi_api_key and self.valves.ncbi_api_key.strip():
params["api_key"] = self.valves.ncbi_api_key.strip()
return params
# ================================================================
# MAIN SEARCH
# ================================================================
def search_pubmed(
self,
query: str = Field(
...,
description="Research question in plain English",
),
max_results: int = Field(
10,
description="How many articles (1-200)",
),
include_abstracts: bool = Field(
True,
description="Include abstracts for AI analysis",
),
) -> str:
"""
Smart PubMed search. Returns numbered references with abstracts.
After searching, ask the AI to summarize or analyze the findings.
"""
try:
max_results = self._safe_int(max_results, 10, 1, 200)
query = str(query).strip()
if not query:
return "Please ask a research question."
if isinstance(include_abstracts, str):
include_abstracts = include_abstracts.lower() not in ("false", "no", "0")
elif not isinstance(include_abstracts, bool):
include_abstracts = True
analysis = self._analyze_via_pubmed(query)
query_type = self._detect_query_type(query)
all_articles, search_log = self._iterative_search(
query, analysis, query_type, max_results
)
if include_abstracts and all_articles:
pmids = [a["pmid"] for a in all_articles]
abstracts = self._fetch_abstracts(pmids)
for article in all_articles:
article["abstract"] = abstracts.get(article["pmid"], "")
scored = self._score_relevance(all_articles, query, query_type)
top = scored[:max_results]
for i, article in enumerate(top):
article["ref_number"] = i + 1
self._last_results = top
self._last_query = query
if not top:
return self._format_no_results(query, analysis, search_log)
return self._format_results(
query, analysis, query_type, search_log,
top, len(all_articles), include_abstracts
)
except Exception as e:
return self._error_msg(str(e))
# ================================================================
# GET RESULTS
# ================================================================
def get_results(
self,
format: str = Field(
"list",
description="'list', 'ris', 'summary', 'abstracts', 'detailed'",
),
) -> str:
"""Get results in different formats."""
try:
fmt = str(format).strip().lower() if format else "list"
if not self._last_results:
return "No stored results. Run search_pubmed first."
if fmt == "ris":
return self._export_ris()
elif fmt == "summary":
return self._synthesize()
elif fmt == "abstracts":
return self._format_abstracts_only()
elif fmt == "detailed":
return self._format_detailed()
else:
return self._format_vancouver_list()
except Exception as e:
return self._error_msg(str(e))
# ================================================================
# PICO SEARCH
# ================================================================
def pico_search(
self,
population: str = Field(..., description="Who?"),
intervention: str = Field("", description="What treatment?"),
comparison: str = Field("", description="Versus?"),
outcome: str = Field("", description="What outcome?"),
max_results: int = Field(15, description="How many (1-200)"),
) -> str:
"""PICO search with abstracts."""
try:
max_results = self._safe_int(max_results, 15, 1, 200)
pico = {}
for label, val in [("Population", population), ("Intervention", intervention),
("Comparison", comparison), ("Outcome", outcome)]:
val = str(val).strip() if val else ""
if val:
pico[label] = val
if not pico:
return "Provide at least a Population."
pico_analysis = {}
for comp, text in pico.items():
pico_analysis[comp] = self._analyze_via_pubmed(text)
all_articles, search_log = self._pico_iterative_search(
pico, pico_analysis, max_results
)
if all_articles:
pmids = [a["pmid"] for a in all_articles]
abstracts = self._fetch_abstracts(pmids)
for a in all_articles:
a["abstract"] = abstracts.get(a["pmid"], "")
combined = " ".join(pico.values())
scored = self._score_relevance(
all_articles, combined, self._detect_query_type(combined)
)
top = scored[:max_results]
for i, a in enumerate(top):
a["ref_number"] = i + 1
self._last_results = top
self._last_query = "PICO: " + "; ".join(k + "=" + v for k, v in pico.items())
md = "# PICO Search Results\n\n"
md += "| Component | Input | MeSH |\n|---|---|---|\n"
for comp, text in pico.items():
mapped = ", ".join(pico_analysis[comp].get("mesh_found", [])[:3]) or text
md += "| " + comp + " | " + text + " | " + mapped + " |\n"
md += "\n"
if top:
md += self._build_evidence_summary(top, combined)
else:
md += "No results found.\n"
md += self._format_next_steps()
return md
except Exception as e:
return self._error_msg(str(e))
# ================================================================
# MESH FINDER
# ================================================================
def find_mesh(
self,
topic: str = Field(..., description="Any medical topic"),
) -> str:
"""Find MeSH terms."""
try:
analysis = self._analyze_via_pubmed(str(topic).strip())
md = "# MeSH: " + topic + "\n\n"
if analysis["mesh_found"]:
md += "| Term | Syntax |\n|---|---|\n"
for t in analysis["mesh_found"]:
md += "| " + t + " | `\"" + t + "\"[MeSH]` |\n"
md += "\n"
if analysis["query_translation"]:
md += "```\n" + analysis["query_translation"] + "\n```\n"
return md
except Exception as e:
return self._error_msg(str(e))
# ================================================================
# PUBMED QUERY ANALYSIS
# ================================================================
def _analyze_via_pubmed(self, query: str) -> Dict:
cache_key = query.lower().strip()
if cache_key in self._cache:
return self._cache[cache_key]
result = {
"original": query,
"query_translation": "",
"mesh_found": [],
"result_count": 0,
}
try:
resp = requests.get(
self.base_url + "/esearch.fcgi",
params=self._api_params({
"db": "pubmed", "term": query,
"retmode": "json", "retmax": "0",
}),
timeout=15,
)
if resp.status_code == 200:
es = resp.json().get("esearchresult", {})
result["result_count"] = int(es.get("count", 0))
result["query_translation"] = es.get("querytranslation", "")
if result["query_translation"]:
mesh = re.findall(
r'"([^"]+)"\[MeSH Terms\]',
result["query_translation"]
)
result["mesh_found"] = list(dict.fromkeys(mesh))
phrase_query = '"' + query + '"[MeSH Terms]'
resp2 = requests.get(
self.base_url + "/esearch.fcgi",
params=self._api_params({
"db": "pubmed", "term": phrase_query,
"retmode": "json", "retmax": "0",
}),
timeout=10,
)
if resp2.status_code == 200:
es2 = resp2.json().get("esearchresult", {})
trans2 = es2.get("querytranslation", "")
if int(es2.get("count", 0)) > 0 and trans2:
for t in re.findall(r'"([^"]+)"\[MeSH Terms\]', trans2):
if t not in result["mesh_found"]:
result["mesh_found"].append(t)
except Exception:
pass
self._cache[cache_key] = result
return result
# ================================================================
# QUERY TYPE
# ================================================================
def _detect_query_type(self, query):
q = query.lower()
for qt, words in {
"guidelines": ["guideline", "guidelines", "protocol", "recommendation", "consensus", "management of"],
"systematic_review": ["systematic review", "meta-analysis"],
"outcomes": ["outcome", "outcomes", "effectiveness", "efficacy"],
"epidemiology": ["prevalence", "incidence", "epidemiology"],
"diagnosis": ["diagnosis", "diagnostic", "screening"],
"treatment": ["treatment", "therapy", "drug", "medication"],
"risk_factors": ["risk factor", "cause", "etiology"],
}.items():
if any(w in q for w in words):
return qt
return "general"
def _get_type_filter(self, qt):
return {
"guidelines": " AND (\"Practice Guideline\"[PT] OR \"Guideline\"[PT] OR guideline[ti])",
"systematic_review": " AND (\"Systematic Review\"[PT] OR \"Meta-Analysis\"[PT])",
"outcomes": " AND (\"Clinical Trial\"[PT] OR \"Comparative Study\"[PT])",
"diagnosis": " AND (diagnosis[ti] OR diagnostic[ti])",
"epidemiology": " AND (prevalence[ti] OR incidence[ti] OR epidemiology[sh])",
"treatment": " AND (\"Clinical Trial\"[PT] OR \"Randomized Controlled Trial\"[PT])",
}.get(qt, "")
# ================================================================
# STRATEGIES
# ================================================================
def _build_strategies(self, query, analysis, query_type):
strategies = []
mesh = analysis.get("mesh_found", [])
count = analysis.get("result_count", 0)
tf = self._get_type_filter(query_type)
if count > 0:
strategies.append(("PubMed Auto-Mapping", query))
if mesh and tf:
mt = ['"' + t + '"[MeSH]' for t in mesh[:4]]
strategies.append(("MeSH + " + query_type, " AND ".join(mt) + tf))
if mesh:
mt = ['"' + t + '"[MeSH]' for t in mesh[:4]]
strategies.append(("MeSH Combined", " AND ".join(mt)))
if len(mesh) >= 2:
strategies.append(("Core MeSH", '"' + mesh[0] + '"[MeSH] AND "' + mesh[1] + '"[MeSH]'))
if mesh and tf:
strategies.append(("Primary MeSH + " + query_type, '"' + mesh[0] + '"[MeSH]' + tf))
strategies.append(("Title/Abstract", "(" + query + ")[tiab]"))
strategies.append(("All Fields", query))
return strategies
def _iterative_search(self, query, analysis, query_type, max_results):
return self._run_strategies(
self._build_strategies(query, analysis, query_type), max_results
)
def _pico_iterative_search(self, pico, pico_analysis, max_results):
strategies = []
comp_parts = []
for comp, analysis in pico_analysis.items():
mesh = analysis.get("mesh_found", [])
if mesh:
if len(mesh) > 1:
mt = ['"' + t + '"[MeSH]' for t in mesh[:3]]
comp_parts.append("(" + " OR ".join(mt) + ")")
else:
comp_parts.append('"' + mesh[0] + '"[MeSH]')
else:
comp_parts.append("(" + pico[comp] + ")")
if len(comp_parts) >= 2:
strategies.append(("Full PICO", " AND ".join(comp_parts)))
combined = " ".join(pico.values())
strategies.append(("Natural Language", combined))
for pn, keys in [("P+I", ["Population", "Intervention"]), ("P+O", ["Population", "Outcome"])]:
parts = []
for k in keys:
if k in pico_analysis:
mesh = pico_analysis[k].get("mesh_found", [])
if mesh:
parts.append('"' + mesh[0] + '"[MeSH]')
elif k in pico:
parts.append(pico[k])
if len(parts) == 2:
strategies.append((pn, " AND ".join(parts)))
strategies.append(("Broad", combined))
return self._run_strategies(strategies, max_results)
def _run_strategies(self, strategies, max_results):
all_articles = []
seen = set()
log = []
for name, sq in strategies:
if not sq:
continue
results = self._run_search(sq, min(max_results * 2, 200))
log.append({"name": name, "query": sq, "found": len(results)})
for a in results:
if a["pmid"] not in seen:
seen.add(a["pmid"])
a["found_via"] = name
all_articles.append(a)
if len(all_articles) >= max_results * 3 and len(log) >= 3:
break
return all_articles, log
# ================================================================
# ABSTRACT FETCHING
# ================================================================
def _fetch_abstracts(self, pmids):
abstracts = {}
if not pmids:
return abstracts
for i in range(0, len(pmids), 25):
batch = pmids[i:i + 25]
try:
resp = requests.get(
self.base_url + "/efetch.fcgi",
params=self._api_params({
"db": "pubmed", "id": ",".join(batch),
"rettype": "xml", "retmode": "xml",
}),
timeout=30,
)
if resp.status_code != 200:
continue
try:
root = ET.fromstring(resp.content)
except ET.ParseError:
self._regex_extract(batch, abstracts, resp.text)
continue
for art in root.findall(".//PubmedArticle"):
pe = art.find(".//PMID")
if pe is None:
continue
pmid = pe.text
parts = []
ae = art.find(".//Abstract")
if ae is not None:
for txt in ae.findall("AbstractText"):
label = txt.get("Label", "")
text = self._elem_text(txt)
if text:
if label:
parts.append(label + ": " + text)
else:
parts.append(text)
if parts:
abstracts[pmid] = " ".join(parts)
mesh_list = [m.text for m in art.findall(".//MeshHeading/DescriptorName") if m.text]
if mesh_list:
abstracts[pmid] += " [MeSH: " + ", ".join(mesh_list[:8]) + "]"
except Exception:
continue
return abstracts
def _elem_text(self, elem):
parts = []
if elem.text:
parts.append(elem.text)
for child in elem:
if child.text:
parts.append(child.text)
if child.tail:
parts.append(child.tail)
return " ".join(parts).strip()
def _regex_extract(self, pmids, abstracts, xml_text):
for pmid in pmids:
if pmid in abstracts:
continue
pattern = r"<PMID[^>]*>" + re.escape(pmid) + r"</PMID>.*?<Abstract>(.*?)</Abstract>"
match = re.search(pattern, xml_text, re.DOTALL)
if match:
text = re.sub(r"<[^>]+>", " ", match.group(1))
text = re.sub(r"\s+", " ", text).strip()
if text:
abstracts[pmid] = text
# ================================================================
# RELEVANCE SCORING
# ================================================================
def _score_relevance(self, articles, query, query_type):
qw = set(re.findall(r"[a-z]{3,}", query.lower())) - {
"the", "and", "for", "with", "from", "that", "this", "are",
"was", "were", "been", "have", "has", "how", "what", "which",
"current", "recent", "new", "using", "based"
}
tw = {
"guidelines": ["guideline", "guidelines", "recommendation", "consensus", "management"],
"systematic_review": ["systematic", "review", "meta-analysis"],
"outcomes": ["outcome", "outcomes", "effectiveness"],
"diagnosis": ["diagnosis", "diagnostic", "screening"],
"treatment": ["treatment", "therapy", "therapeutic"],
"epidemiology": ["prevalence", "incidence", "epidemiology"],
}
for a in articles:
s = 0
tl = a.get("title", "").lower()
ab = a.get("abstract", "").lower()
s += len(qw & set(re.findall(r"[a-z]{3,}", tl))) * 5
if ab:
s += min(15, len(qw & set(re.findall(r"[a-z]{3,}", ab))) * 2)
s += 3
for w in tw.get(query_type, []):
if w in tl: s += 10
if w in ab: s += 3
year = self._extract_year(a.get("pubdate", ""))
if year:
if year >= 2023: s += 8
elif year >= 2020: s += 5
elif year >= 2015: s += 2
jl = a.get("journal", "").lower()
if any(j in jl for j in ["lancet", "bmj", "jama", "new england", "cochrane", "pediatrics"]):
s += 5
a["relevance_score"] = s
articles.sort(key=lambda x: x.get("relevance_score", 0), reverse=True)
return articles
# ================================================================
# SEARCH API
# ================================================================
def _run_search(self, query, max_results):
max_results = self._safe_int(max_results, 10, 1, 200)
try:
resp = requests.get(
self.base_url + "/esearch.fcgi",
params=self._api_params({
"db": "pubmed", "term": str(query),
"retmode": "json", "retmax": str(max_results),
"sort": "relevance",
}),
timeout=20,
)
resp.raise_for_status()
es = resp.json().get("esearchresult", {})
if "ERROR" in es:
return []
ids = es.get("idlist", es.get("IdList", []))
if not ids:
return []
resp = requests.get(
self.base_url + "/esummary.fcgi",
params=self._api_params({"db": "pubmed", "id": ",".join(ids), "retmode": "json"}),
timeout=20,
)
resp.raise_for_status()
sums = resp.json().get("result", {})
articles = []
for aid in ids:
if aid not in sums or not isinstance(sums[aid], dict):
continue
art = sums[aid]
if "title" not in art:
continue
articles.append({
"title": art.get("title", "Untitled"),
"authors": ", ".join(
a["name"] for a in art.get("authors", [])
if isinstance(a, dict) and a.get("name")
),
"pubdate": art.get("pubdate", ""),
"journal": art.get("fulljournalname", ""),
"volume": art.get("volume", ""),
"issue": art.get("issue", ""),
"pages": art.get("pages", ""),
"doi": next(
(x["value"] for x in art.get("articleids", [])
if isinstance(x, dict) and x.get("idtype") == "doi"), ""
),
"pmid": aid,
"url": "https://pubmed.ncbi.nlm.nih.gov/" + aid + "/",
"abstract": "",
"ref_number": 0,
})
return articles
except Exception:
return []
# ================================================================
# KEY INNOVATION: EVIDENCE SUMMARY WITH EMBEDDED CITATIONS
# Instead of separate instructions, we embed the citation
# directly into the content the AI reads
# ================================================================
def _build_evidence_summary(self, articles, query):
"""
Build a structured evidence block where each piece of
information is ALREADY tagged with its reference number.
The AI just reads this and naturally uses the numbers.
"""
md = "## Evidence from " + str(len(articles)) + " articles\n\n"
md += "Below is the evidence found. Each finding is tagged with its reference number.\n\n"
for a in articles:
ref = a.get("ref_number", 0)
yr = self._extract_year(a.get("pubdate", "")) or "n.d."
auth_list = a.get("authors", "").split(", ")
first = auth_list[0] if auth_list and auth_list[0] else "Unknown"
journal = a.get("journal", "")
md += "---\n\n"
md += "**REFERENCE [" + str(ref) + "]:** "
md += first + " et al. (" + str(yr) + "). "
md += a.get("title", "") + ". "
md += "*" + journal + "*."
if a.get("doi"):
md += " doi:" + a["doi"]
md += " PMID:" + a.get("pmid", "") + "\n\n"
if a.get("abstract"):
md += "**FINDINGS FROM [" + str(ref) + "]:** "
md += a["abstract"] + "\n\n"
else:
md += "**[" + str(ref) + "]:** No abstract available.\n\n"
md += "---\n\n"
# Vancouver list
md += "## Reference List\n\n"
for a in articles:
md += self._vancouver_ref(a) + "\n\n"
# Short clear instruction
md += "---\n\n"
md += "IMPORTANT: When discussing any finding above, "
md += "cite it as [1], [2], etc. matching the reference numbers. "
md += "Example: 'Inhaled bronchodilators are first-line [1]. "
md += "Oral corticosteroids reduce hospitalization [2,3].'\n\n"
return md
# ================================================================
# MAIN FORMATTER
# ================================================================
def _format_results(self, query, analysis, query_type, search_log, articles, total, show_abs):
md = "# PubMed Results: " + query + "\n\n"
# Brief query info
if analysis["mesh_found"]:
md += "**MeSH:** " + ", ".join(analysis["mesh_found"]) + "\n"
md += "**Found:** " + str(total) + " candidates → top " + str(len(articles)) + " shown\n\n"
# The evidence summary with embedded citations
md += self._build_evidence_summary(articles, query)
md += self._format_next_steps()
return md
# ================================================================
# VANCOUVER REFERENCE
# ================================================================
def _vancouver_ref(self, article):
ref_num = article.get("ref_number", 0)
parts = []
authors = article.get("authors", "")
if authors:
al = [a.strip() for a in authors.split(",") if a.strip()]
if len(al) > 6:
parts.append(", ".join(al[:6]) + ", et al.")
else:
parts.append(", ".join(al) + ".")
else:
parts.append("[No authors].")
parts.append(article.get("title", "Untitled").rstrip(".") + ".")
if article.get("journal"):
parts.append(article["journal"] + ".")
yr = self._extract_year(article.get("pubdate", ""))
pd = str(yr) if yr else ""
vol = article.get("volume", "")
if vol:
if pd: pd += ";"
pd += vol
if article.get("issue"): pd += "(" + article["issue"] + ")"
if article.get("pages"): pd += ":" + article["pages"]
if pd:
parts.append(pd + ".")
if article.get("doi"):
parts.append("doi:" + article["doi"] + ".")
if article.get("pmid"):
parts.append("PMID:" + article["pmid"] + ".")
return "[" + str(ref_num) + "] " + " ".join(parts)
def _build_vancouver_list(self, articles):
md = ""
for a in articles:
md += self._vancouver_ref(a) + "\n\n"
return md
# ================================================================
# NEXT STEPS
# ================================================================
def _format_next_steps(self):
return (
"\n## Next Steps\n\n"
"| Command | Output |\n|---|---|\n"
"| `get results as list` | Vancouver references |\n"
"| `get results as ris` | Zotero RIS file |\n"
"| `get results as summary` | Theme analysis |\n"
"| `get results as abstracts` | All abstracts |\n"
"| `get results as detailed` | Full metadata |\n\n"
)
def _format_no_results(self, query, analysis, search_log):
md = "# No Results: " + query + "\n\n"
if analysis["query_translation"]:
md += "```\n" + analysis["query_translation"] + "\n```\n\n"
for s in search_log:
md += "- " + s["name"] + ": `" + s["query"] + "` (0 results)\n"
md += "\nTry simpler terms.\n"
return md
# ================================================================
# OUTPUT FORMATS
# ================================================================
def _format_vancouver_list(self):
md = "# References (" + str(len(self._last_results)) + ")\n\n"
md += self._build_vancouver_list(self._last_results)
return md
def _export_ris(self):
ris = ""
for a in self._last_results:
ris += self._to_ris(a)
return (
"# RIS Export (" + str(len(self._last_results)) + ")\n\n"
"Copy → save as .ris → Zotero Import\n\n"
"```ris\n" + ris + "```\n"
)
def _format_abstracts_only(self):
md = "# Abstracts (" + str(len(self._last_results)) + ")\n\n"
for a in self._last_results:
ref = a.get("ref_number", 0)
md += "## [" + str(ref) + "] " + a.get("title", "") + "\n\n"
if a.get("abstract"):
md += a["abstract"] + "\n\n"
else:
md += "No abstract.\n\n"
md += "---\n\n"
return md
def _synthesize(self):
articles = self._last_results
md = "# Summary: " + self._last_query + "\n\n"
md += str(len(articles)) + " articles analyzed.\n\n"
# Themes
all_text = " ".join(a.get("title", "") + " " + a.get("abstract", "") for a in articles)
wf = {}
stops = {
"the", "and", "for", "with", "from", "that", "this", "was", "were",
"been", "have", "has", "study", "review", "patients", "results",
"methods", "conclusion", "background", "objective", "clinical",
"using", "based", "among", "between", "group", "data", "also"
}
for w in re.findall(r"[a-z]{4,}", all_text.lower()):
if w not in stops:
wf[w] = wf.get(w, 0) + 1
md += "## Themes\n\n"
for w, c in sorted(wf.items(), key=lambda x: -x[1])[:12]:
if c >= 3:
md += "- " + w + " (" + str(c) + "x)\n"
md += "\n"
# Evidence with embedded citations
md += self._build_evidence_summary(articles, self._last_query)
return md
def _format_detailed(self):
md = "# Detailed (" + str(len(self._last_results)) + ")\n\n"
for a in self._last_results:
ref = a.get("ref_number", 0)
md += "## [" + str(ref) + "] " + a.get("title", "") + "\n\n"
md += "- Authors: " + a.get("authors", "?") + "\n"
md += "- Journal: " + a.get("journal", "?") + "\n"
md += "- Date: " + a.get("pubdate", "?") + "\n"
if a.get("doi"):
md += "- DOI: " + a["doi"] + "\n"
md += "- PMID: " + a["pmid"] + "\n"
md += "- Score: " + str(a.get("relevance_score", 0)) + "\n"
if a.get("abstract"):
md += "\n" + a["abstract"] + "\n"
md += "\n---\n\n"
return md
# ================================================================
# UTILITIES
# ================================================================
def _to_ris(self, a):
ris = "TY - JOUR\n"
if a.get("authors"):
for au in a["authors"].split(", "):
if au.strip():
ris += "AU - " + au.strip() + "\n"
ris += "T1 - " + a.get("title", "").rstrip(".") + "\n"
if a.get("journal"):
ris += "JO - " + a["journal"] + "\n"
if a.get("pubdate"):
m = re.search(r"(\d{4})", a["pubdate"])
if m:
ris += "PY - " + m.group(1) + "\n"
ris += "DA - " + a["pubdate"] + "\n"
if a.get("volume"):
ris += "VL - " + a["volume"] + "\n"
if a.get("issue"):
ris += "IS - " + a["issue"] + "\n"
if a.get("pages"):
if "-" in a["pages"]:
sp, ep = a["pages"].split("-", 1)
ris += "SP - " + sp.strip() + "\n"
ris += "EP - " + ep.strip() + "\n"
else:
ris += "SP - " + a["pages"] + "\n"
if a.get("doi"):
ris += "DO - " + a["doi"] + "\n"
if a.get("url"):
ris += "UR - " + a["url"] + "\n"
if a.get("abstract"):
ab = re.sub(r"\[MeSH:.*?\]", "", a["abstract"][:2000])
ris += "AB - " + ab.strip() + "\n"
ris += "ER -\n\n"
return ris
def _extract_year(self, d):
if not d:
return None
m = re.search(r"(\d{4})", str(d))
return int(m.group(1)) if m else None
def _safe_int(self, v, default=10, mn=1, mx=200):
try:
r = int(float(str(v)))
except (TypeError, ValueError):
r = default
return max(mn, min(mx, r))
def _error_msg(self, msg):
return "Error: " + msg + "\n\nTry simpler terms or find_mesh."