Spaces:
Runtime error
Runtime error
| import asyncio | |
| from typing import List, Dict, Any, Optional | |
| from backend.providers.openalex import search_openalex | |
| from backend.providers.semantic_scholar import search_semantic_scholar | |
| from backend.providers.pubmed import search_pubmed | |
| from backend.providers.arxiv import search_arxiv | |
| from backend.providers.crossref import search_crossref | |
| from backend.providers.dblp import search_dblp | |
| from backend.providers.latam_repositories import search_alicia, search_la_referencia, search_bDTD, search_rraae | |
| from backend.providers.scopus import search_scopus | |
| from backend.providers.zenodo import search_zenodo | |
| from backend.providers.openaire import search_openaire | |
| from backend.providers.doaj import search_doaj | |
| from backend.providers.core_ import search_core | |
| from backend.providers.redalyc import search_redalyc | |
| from backend.providers.serpapi import search_serpapi | |
| from backend.providers.sources import SOURCE_GROUPS, SOURCE_ALIASES | |
| from modules.config.sources_config_tab import get_enabled_sources | |
| # Provider map | |
| PROVIDERS = { | |
| "openalex": search_openalex, | |
| "semantic": search_semantic_scholar, | |
| "pubmed": search_pubmed, | |
| "arxiv": search_arxiv, | |
| "crossref": search_crossref, | |
| "dblp": search_dblp, | |
| "alicia": search_alicia, | |
| "renati": search_alicia, # Alias to ALICIA as fallback since RENATI API is blocked | |
| "lareferencia": search_la_referencia, | |
| "bdtd": search_bDTD, | |
| "rraae": search_rraae, | |
| "scopus": search_scopus, | |
| "zenodo": search_zenodo, | |
| "openaire": search_openaire, | |
| "doaj": search_doaj, | |
| "core": search_core, | |
| "redalyc": search_redalyc, | |
| "serpapi": search_serpapi, | |
| } | |
| def expand_sources(sources: List[str]) -> List[str]: | |
| """Expand source groups and aliases to individual source IDs.""" | |
| expanded = set() | |
| for s in sources: | |
| s_lower = s.lower().strip() | |
| aliased = SOURCE_ALIASES.get(s_lower, s_lower) | |
| if aliased in SOURCE_GROUPS: | |
| for src in SOURCE_GROUPS[aliased]: | |
| expanded.add(src) | |
| else: | |
| expanded.add(aliased) | |
| return list(expanded) | |
| def deduplicate_results(results: List[dict]) -> List[dict]: | |
| """Deduplicate by DOI and normalized title.""" | |
| seen = {} | |
| for r in results: | |
| doi = (r.get("doi") or "").lower().strip() | |
| if doi: | |
| key = f"doi:{doi}" | |
| if key in seen: | |
| continue | |
| seen[key] = r | |
| continue | |
| title = (r.get("title") or "").lower().strip()[:60] | |
| if title: | |
| key = f"title:{title}" | |
| if key in seen: | |
| continue | |
| seen[key] = r | |
| return list(seen.values()) | |
| async def search(query: str, sources: List[str] = None, max_results: int = 50, | |
| year_start: str = None, year_end: str = None, | |
| enabled_sources: Optional[List[str]] = None) -> dict: | |
| """Search across multiple academic sources.""" | |
| if not sources: | |
| sources = ["latam", "global"] | |
| expanded = expand_sources(sources) | |
| # If no explicit enabled_sources passed, use the global config | |
| if enabled_sources is None: | |
| enabled_sources = get_enabled_sources() | |
| if enabled_sources: | |
| expanded = [src for src in expanded if src in enabled_sources] | |
| tasks = [] | |
| for src in expanded: | |
| if src in PROVIDERS: | |
| tasks.append(PROVIDERS[src](query, limit=min(max_results, 50))) | |
| # Run all searches in parallel | |
| raw_results = await asyncio.gather(*tasks, return_exceptions=True) | |
| # Flatten and filter exceptions | |
| all_results = [] | |
| for r in raw_results: | |
| if isinstance(r, list): | |
| all_results.extend(r) | |
| # Apply year filters | |
| if year_start: | |
| all_results = [r for r in all_results if r.get("year") and int(r.get("year", 0)) >= int(year_start)] | |
| if year_end: | |
| all_results = [r for r in all_results if r.get("year") and int(r.get("year", 0)) <= int(year_end)] | |
| # Fallback Manager (Rescue Queries) | |
| if len(all_results) < 5 and any(s in ["alicia", "renati", "lareferencia"] for s in expanded): | |
| try: | |
| rescue = await PROVIDERS["openalex"](query, limit=10) | |
| if rescue: | |
| for r in rescue: | |
| r["source"] = f"{r.get('source', 'OpenAlex')} (Rescue)" | |
| all_results.extend(rescue) | |
| except Exception as e: | |
| pass | |
| # Deduplicate | |
| deduplicated = deduplicate_results(all_results) | |
| # Sort by year (newest first) and limit | |
| deduplicated.sort(key=lambda x: x.get("year") or 0, reverse=True) | |
| results = deduplicated[:max_results] | |
| return { | |
| "results": results, | |
| "total": len(results), | |
| "totalBeforeDedup": len(all_results), | |
| "sourcesUsed": expanded, | |
| } | |