nikeshn commited on
Commit
c0e427b
·
verified ·
1 Parent(s): 2ef66f7

Create search.py

Browse files
Files changed (1) hide show
  1. src/api/search.py +156 -0
src/api/search.py ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException
2
+ from pydantic import BaseModel
3
+ from typing import Optional
4
+ import httpx
5
+ import os
6
+
7
+ router = APIRouter()
8
+
9
+ class SearchRequest(BaseModel):
10
+ query: str
11
+ source: str = "primo"
12
+ limit: int = 5
13
+ peer_reviewed: bool = False
14
+ open_access: bool = False
15
+ year_from: Optional[str] = None
16
+ year_to: Optional[str] = None
17
+
18
+ @router.post("")
19
+ async def search_endpoint(req: SearchRequest):
20
+ settings = get_settings()
21
+
22
+ if req.source == "primo":
23
+ return await search_primo(req, settings.primo_api_key)
24
+ elif req.source == "pubmed":
25
+ return await search_pubmed(req)
26
+ elif req.source == "scholar":
27
+ return await search_scholar(req)
28
+ else:
29
+ raise HTTPException(status_code=400, detail=f"Unknown source: {req.source}")
30
+
31
+ async def search_primo(req: SearchRequest, api_key: str):
32
+ if not api_key:
33
+ return {"error": "PRIMO_API_KEY not configured", "results": []}
34
+
35
+ # Simple query for now
36
+ from urllib.parse import quote
37
+ query = quote(req.query)
38
+
39
+ url = f"https://api-eu.hosted.exlibrisgroup.com/primo/v1/search?vid=971KUOSTAR_INST:KU&tab=Everything&scope=MyInst_and_CI&q=any,contains,{query}&lang=en&sort=rank&limit={req.limit}&offset=0&mode=advanced&apikey={api_key}"
40
+
41
+ try:
42
+ async with httpx.AsyncClient(timeout=15) as client:
43
+ r = await client.get(url, headers={"Accept": "application/json"})
44
+ if r.status_code != 200:
45
+ return {"error": f"PRIMO API error: {r.status_code}", "results": []}
46
+
47
+ data = r.json()
48
+ results = []
49
+ for doc in data.get("docs", [])[:req.limit]:
50
+ pnx = doc.get("pnx", {})
51
+ d = pnx.get("display", {})
52
+ a = pnx.get("addata", {})
53
+
54
+ results.append({
55
+ "title": (d.get("title") or ["Untitled"])[0],
56
+ "creator": "; ".join(d.get("creator") or d.get("contributor") or []) or "Unknown",
57
+ "date": (d.get("date") or [""])[0],
58
+ "type": (d.get("type") or [""])[0],
59
+ "source": (d.get("source") or [""])[0],
60
+ "primo_url": f"https://khalifa.primo.exlibrisgroup.com/discovery/search?query=any,contains,{query}&vid=971KUOSTAR_INST:KU",
61
+ "_source": "PRIMO"
62
+ })
63
+
64
+ return {"total": data.get("info", {}).get("total", 0), "results": results, "source": "PRIMO"}
65
+
66
+ except Exception as e:
67
+ return {"error": str(e), "results": []}
68
+
69
+ async def search_pubmed(req: SearchRequest):
70
+ try:
71
+ async with httpx.AsyncClient(timeout=15) as client:
72
+ # Search
73
+ search_url = f"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&term={req.query}&retmax={req.limit}&retmode=json"
74
+ r = await client.get(search_url)
75
+ if r.status_code != 200:
76
+ return {"error": "PubMed search failed", "results": []}
77
+
78
+ ids = r.json().get("esearchresult", {}).get("idlist", [])
79
+ if not ids:
80
+ return {"total": 0, "results": [], "source": "PubMed"}
81
+
82
+ # Fetch details
83
+ fetch_url = f"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&id={','.join(ids)}&retmode=json"
84
+ r2 = await client.get(fetch_url)
85
+ if r2.status_code != 200:
86
+ return {"error": "PubMed fetch failed", "results": []}
87
+
88
+ data = r2.json().get("result", {})
89
+ results = []
90
+ for pid in ids:
91
+ rec = data.get(pid, {})
92
+ if not isinstance(rec, dict):
93
+ continue
94
+ authors = ", ".join(a.get("name", "") for a in rec.get("authors", [])[:3])
95
+ if len(rec.get("authors", [])) > 3:
96
+ authors += " et al."
97
+
98
+ results.append({
99
+ "title": rec.get("title", ""),
100
+ "creator": authors,
101
+ "date": rec.get("pubdate", ""),
102
+ "source": rec.get("fulljournalname", rec.get("source", "")),
103
+ "pmid": pid,
104
+ "link": f"https://pubmed.ncbi.nlm.nih.gov/{pid}/",
105
+ "type": "Journal Article",
106
+ "_source": "PubMed"
107
+ })
108
+
109
+ return {"total": len(ids), "results": results, "source": "PubMed"}
110
+
111
+ except Exception as e:
112
+ return {"error": str(e), "results": []}
113
+
114
+ async def search_scholar(req: SearchRequest):
115
+ # Semantic Scholar implementation
116
+ try:
117
+ url = "https://api.semanticscholar.org/graph/v1/paper/search"
118
+ params = {
119
+ "query": req.query,
120
+ "limit": req.limit,
121
+ "fields": "title,authors,year,venue,externalIds,citationCount"
122
+ }
123
+
124
+ async with httpx.AsyncClient(timeout=15) as client:
125
+ r = await client.get(url, params=params)
126
+ if r.status_code != 200:
127
+ return {"error": f"Semantic Scholar API: {r.status_code}", "results": []}
128
+
129
+ data = r.json()
130
+ results = []
131
+ for paper in data.get("data", []):
132
+ authors = ", ".join(a.get("name", "") for a in (paper.get("authors") or [])[:3])
133
+ if len(paper.get("authors") or []) > 3:
134
+ authors += " et al."
135
+
136
+ ext_ids = paper.get("externalIds") or {}
137
+
138
+ results.append({
139
+ "title": paper.get("title", ""),
140
+ "creator": authors,
141
+ "date": str(paper.get("year", "")),
142
+ "source": paper.get("venue", ""),
143
+ "citations": paper.get("citationCount", 0),
144
+ "link": f"https://www.semanticscholar.org/paper/{paper.get('paperId')}" if paper.get("paperId") else None,
145
+ "type": "Article",
146
+ "_source": "Semantic Scholar"
147
+ })
148
+
149
+ return {"total": data.get("total", 0), "results": results, "source": "Semantic Scholar"}
150
+
151
+ except Exception as e:
152
+ return {"error": str(e), "results": []}
153
+
154
+ def get_settings():
155
+ from src.config import get_settings as gs
156
+ return gs()