arxplorer / src /controller /recommendation_controller.py
Subhadeep Mandal
Fresh deploy
54eb2ce
Raw
History Blame Contribute Delete
5.43 kB
import asyncio
from fastapi import HTTPException
from sqlalchemy import select
from ..database.db import session_pool
from ..model.paper import Paper
from ..model.arxiv_catalog import ArxivCatalog
from ..schema.paper import RecommendationItem, RecommendationRequest, RecommendationsResponse
from ..core.recommendation_engine.recommender import (
get_similar_papers,
get_similar_on_topic,
get_papers_by_authors,
rerank_results,
)
from ..core.logger import SingletonLogger
logger = SingletonLogger().get_logger()
async def get_recommendations_by_metadata(
req: RecommendationRequest,
user_id: int,
limit: int = 10,
) -> RecommendationsResponse:
async with session_pool() as session:
all_papers_result = await session.execute(
select(Paper.arxiv_id).where(
Paper.user_id == user_id, Paper.arxiv_id.isnot(None)
)
)
saved_arxiv_ids = [row[0] for row in all_papers_result.all()]
user_saved_categories: list[str] = []
if saved_arxiv_ids:
catalog_result = await session.execute(
select(ArxivCatalog.primary_category)
.where(ArxivCatalog.arxiv_id.in_(saved_arxiv_ids))
.distinct()
)
user_saved_categories = [
row[0] for row in catalog_result.all() if row[0]
]
exclude_ids = saved_arxiv_ids
if req.arxiv_id:
exclude_ids = list(set(saved_arxiv_ids + [req.arxiv_id]))
primary_category = req.primary_category
async def _empty_list() -> list:
return []
similar_task = get_similar_papers(
req.title, req.abstract, exclude_ids, top_k=limit * 2
)
on_topic_task = (
get_similar_on_topic(
req.title, req.abstract, primary_category, exclude_ids, top_k=limit
)
if primary_category
else _empty_list()
)
by_authors_task = get_papers_by_authors(
req.authors, exclude_ids, top_k=limit
)
similar, on_topic, by_authors = await asyncio.gather(
similar_task, on_topic_task, by_authors_task
)
reranked_similar = rerank_results(similar, user_saved_categories)[:limit]
return RecommendationsResponse(
similar_papers=[RecommendationItem(**p) for p in reranked_similar],
on_this_topic=[RecommendationItem(**p) for p in on_topic[:limit]],
from_these_authors=[RecommendationItem(**p) for p in by_authors[:limit]],
)
async def get_recommendations_for_paper(
paper_id: int,
user_id: int,
limit: int = 10,
) -> RecommendationsResponse:
async with session_pool() as session:
result = await session.execute(
select(Paper).where(Paper.id == paper_id, Paper.user_id == user_id)
)
paper = result.scalar_one_or_none()
if not paper:
raise HTTPException(status_code=404, detail="Paper not found")
all_papers_result = await session.execute(
select(Paper.arxiv_id).where(
Paper.user_id == user_id, Paper.arxiv_id.isnot(None)
)
)
saved_arxiv_ids = [row[0] for row in all_papers_result.all()]
# Cross-reference with catalog for category affinity
user_saved_categories: list[str] = []
if saved_arxiv_ids:
catalog_result = await session.execute(
select(ArxivCatalog.primary_category)
.where(ArxivCatalog.arxiv_id.in_(saved_arxiv_ids))
.distinct()
)
user_saved_categories = [
row[0] for row in catalog_result.all() if row[0]
]
# Determine primary_category for on-topic query
primary_category = None
if paper.arxiv_id:
cat_result = await session.execute(
select(ArxivCatalog.primary_category).where(
ArxivCatalog.arxiv_id == paper.arxiv_id
)
)
row = cat_result.first()
if row:
primary_category = row[0]
# Fall back to first topic from Paper.topics
if not primary_category and paper.topics:
primary_category = paper.topics.split()[0] if paper.topics else None
async def _empty_list() -> list:
return []
# Run all three recommendation queries in parallel
similar_task = get_similar_papers(
paper.title, paper.abstract, saved_arxiv_ids, top_k=limit * 2
)
on_topic_task = (
get_similar_on_topic(
paper.title, paper.abstract, primary_category, saved_arxiv_ids, top_k=limit
)
if primary_category
else _empty_list()
)
by_authors_task = get_papers_by_authors(
paper.authors, saved_arxiv_ids, top_k=limit
)
similar, on_topic, by_authors = await asyncio.gather(
similar_task, on_topic_task, by_authors_task
)
reranked_similar = rerank_results(similar, user_saved_categories)[:limit]
return RecommendationsResponse(
similar_papers=[RecommendationItem(**p) for p in reranked_similar],
on_this_topic=[RecommendationItem(**p) for p in on_topic[:limit]],
from_these_authors=[RecommendationItem(**p) for p in by_authors[:limit]],
)