File size: 3,545 Bytes
54eb2ce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
from sqlalchemy import select, desc

from ..database.db import session_pool
from ..model.paper import Paper
from ..model.arxiv_catalog import ArxivCatalog
from ..schema.arxiv import ArxivEntry
from ..controller.catalog_controller import get_feed
from ..core.recommendation_engine.recommender import (
    get_feed_recommendations,
    rerank_results,
    search_catalog,
)
from ..core.logger import SingletonLogger

logger = SingletonLogger().get_logger()

MAX_SEED_PAPERS = 5


def _qdrant_results_to_entries(items: list[dict]) -> list[ArxivEntry]:
    entries: list[ArxivEntry] = []
    for item in items:
        authors_str = item.get("authors") or ""
        authors = [a.strip() for a in authors_str.split(";") if a.strip()] if authors_str else []
        categories_str = item.get("categories") or ""
        categories = categories_str.split() if categories_str else []

        entries.append(
            ArxivEntry(
                id=str(item.get("arxiv_id") or ""),
                arxiv_id=item.get("arxiv_id"),
                title=item.get("title") or "Untitled",
                abstract=item.get("abstract") or "",
                authors=authors,
                categories=categories,
                primary_category=item.get("primary_category"),
                published=item.get("published_date"),
                updated=None,
                pdf_url=item.get("pdf_url"),
                paper_url=item.get("paper_url"),
            )
        )
    return entries


async def search_catalog_papers(

    query: str,

    start: int = 0,

    limit: int = 20,

) -> list[ArxivEntry]:
    try:
        results = await search_catalog(query=query, offset=start, limit=limit)
        return _qdrant_results_to_entries(results)
    except Exception as exc:
        logger.error("Catalog search failed: %s", exc)
        raise


async def get_smart_feed(

    user_id: int,

    topics: list[str],

    start: int = 0,

    limit: int = 24,

) -> list[ArxivEntry]:
    async with session_pool() as session:
        saved_result = await session.execute(
            select(Paper)
            .where(Paper.user_id == user_id, Paper.arxiv_id.isnot(None))
            .order_by(desc(Paper.created_at))
        )
        saved_papers = saved_result.scalars().all()

    if not saved_papers:
        return await get_feed(topics=topics, start=start, limit=limit)

    saved_arxiv_ids = [p.arxiv_id for p in saved_papers if p.arxiv_id]

    seed_papers = saved_papers[:MAX_SEED_PAPERS]
    query_texts = [
        f"{p.title} {(p.abstract or '')[:200]}" for p in seed_papers
    ]

    async with session_pool() as session:
        cat_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 cat_result.all() if row[0]]

    try:
        results = await get_feed_recommendations(
            query_texts=query_texts,
            category_filter=topics,
            exclude_ids=saved_arxiv_ids,
            offset=start,
            limit=limit,
        )
        reranked = rerank_results(results, user_saved_categories)[:limit]
        return _qdrant_results_to_entries(reranked)
    except Exception as exc:
        logger.warning("Qdrant unavailable, falling back to catalog feed: %s", exc)
        return await get_feed(topics=topics, start=start, limit=limit)