205 lines
6 KiB
Python
205 lines
6 KiB
Python
from __future__ import annotations
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.services.domains.publications.modes import (
|
|
MODE_ALL,
|
|
MODE_LATEST,
|
|
MODE_UNREAD,
|
|
resolve_publication_view_mode,
|
|
)
|
|
from app.services.domains.publications.queries import (
|
|
get_latest_completed_run_id_for_user,
|
|
get_publication_item_for_user,
|
|
publication_list_item_from_row,
|
|
publications_query,
|
|
unread_item_from_row,
|
|
)
|
|
from app.services.domains.publications.types import PublicationListItem, UnreadPublicationItem
|
|
from app.services.domains.unpaywall.application import resolve_publication_oa_metadata
|
|
from sqlalchemy import update
|
|
from app.db.models import Publication
|
|
|
|
|
|
def _with_oa_overrides(
|
|
rows: list[PublicationListItem],
|
|
oa_data: dict[int, tuple[str | None, str | None]],
|
|
) -> list[PublicationListItem]:
|
|
return [
|
|
PublicationListItem(
|
|
publication_id=row.publication_id,
|
|
scholar_profile_id=row.scholar_profile_id,
|
|
scholar_label=row.scholar_label,
|
|
title=row.title,
|
|
year=row.year,
|
|
citation_count=row.citation_count,
|
|
venue_text=row.venue_text,
|
|
pub_url=row.pub_url,
|
|
doi=(oa_data.get(row.publication_id) or (None, None))[0] or row.doi,
|
|
pdf_url=(oa_data.get(row.publication_id) or (None, None))[1] or row.pdf_url,
|
|
is_read=row.is_read,
|
|
first_seen_at=row.first_seen_at,
|
|
is_new_in_latest_run=row.is_new_in_latest_run,
|
|
)
|
|
for row in rows
|
|
]
|
|
|
|
|
|
def _resolved_fields(
|
|
*,
|
|
row: PublicationListItem,
|
|
resolved: tuple[str | None, str | None] | None,
|
|
) -> tuple[str | None, str | None]:
|
|
if resolved is None:
|
|
return row.doi, row.pdf_url
|
|
resolved_doi, resolved_pdf = resolved
|
|
return resolved_doi or row.doi, resolved_pdf or row.pdf_url
|
|
|
|
|
|
async def _persist_resolved_metadata(
|
|
db_session: AsyncSession,
|
|
*,
|
|
rows: list[PublicationListItem],
|
|
oa_data: dict[int, tuple[str | None, str | None]],
|
|
) -> None:
|
|
by_id = {row.publication_id: row for row in rows}
|
|
updates: list[tuple[int, str | None, str | None]] = []
|
|
for publication_id, resolved in oa_data.items():
|
|
existing = by_id.get(publication_id)
|
|
if existing is None:
|
|
continue
|
|
next_doi, next_pdf = _resolved_fields(row=existing, resolved=resolved)
|
|
if next_doi == existing.doi and next_pdf == existing.pdf_url:
|
|
continue
|
|
updates.append((publication_id, next_doi, next_pdf))
|
|
for publication_id, doi, pdf_url in updates:
|
|
await db_session.execute(
|
|
update(Publication)
|
|
.where(Publication.id == publication_id)
|
|
.values(doi=doi, pdf_url=pdf_url)
|
|
)
|
|
if updates:
|
|
await db_session.commit()
|
|
|
|
|
|
def missing_pdf_items(
|
|
rows: list[PublicationListItem],
|
|
*,
|
|
limit: int,
|
|
) -> list[PublicationListItem]:
|
|
bounded_limit = max(0, int(limit))
|
|
if bounded_limit == 0:
|
|
return []
|
|
return [row for row in rows if not row.pdf_url][:bounded_limit]
|
|
|
|
|
|
async def resolve_and_persist_oa_metadata(
|
|
db_session: AsyncSession,
|
|
*,
|
|
rows: list[PublicationListItem],
|
|
unpaywall_email: str | None = None,
|
|
) -> dict[int, tuple[str | None, str | None]]:
|
|
if not rows:
|
|
return {}
|
|
oa_data = await resolve_publication_oa_metadata(rows, request_email=unpaywall_email)
|
|
await _persist_resolved_metadata(db_session, rows=rows, oa_data=oa_data)
|
|
return oa_data
|
|
|
|
|
|
async def list_for_user(
|
|
db_session: AsyncSession,
|
|
*,
|
|
user_id: int,
|
|
mode: str = MODE_ALL,
|
|
scholar_profile_id: int | None = None,
|
|
limit: int = 300,
|
|
) -> list[PublicationListItem]:
|
|
resolved_mode = resolve_publication_view_mode(mode)
|
|
latest_run_id = await get_latest_completed_run_id_for_user(db_session, user_id=user_id)
|
|
result = await db_session.execute(
|
|
publications_query(
|
|
user_id=user_id,
|
|
mode=resolved_mode,
|
|
latest_run_id=latest_run_id,
|
|
scholar_profile_id=scholar_profile_id,
|
|
limit=limit,
|
|
)
|
|
)
|
|
rows = [
|
|
publication_list_item_from_row(row, latest_run_id=latest_run_id)
|
|
for row in result.all()
|
|
]
|
|
return rows
|
|
|
|
|
|
async def retry_pdf_for_user(
|
|
db_session: AsyncSession,
|
|
*,
|
|
user_id: int,
|
|
scholar_profile_id: int,
|
|
publication_id: int,
|
|
unpaywall_email: str | None = None,
|
|
) -> PublicationListItem | None:
|
|
item = await get_publication_item_for_user(
|
|
db_session,
|
|
user_id=user_id,
|
|
scholar_profile_id=scholar_profile_id,
|
|
publication_id=publication_id,
|
|
)
|
|
if item is None:
|
|
return None
|
|
oa_data = await resolve_and_persist_oa_metadata(
|
|
db_session,
|
|
rows=[item],
|
|
unpaywall_email=unpaywall_email,
|
|
)
|
|
return _with_oa_overrides([item], oa_data)[0]
|
|
|
|
|
|
async def list_unread_for_user(
|
|
db_session: AsyncSession,
|
|
*,
|
|
user_id: int,
|
|
limit: int = 100,
|
|
) -> list[UnreadPublicationItem]:
|
|
result = await db_session.execute(
|
|
publications_query(
|
|
user_id=user_id,
|
|
mode=MODE_UNREAD,
|
|
latest_run_id=None,
|
|
scholar_profile_id=None,
|
|
limit=limit,
|
|
)
|
|
)
|
|
return [unread_item_from_row(row) for row in result.all()]
|
|
|
|
|
|
async def list_new_for_latest_run_for_user(
|
|
db_session: AsyncSession,
|
|
*,
|
|
user_id: int,
|
|
limit: int = 100,
|
|
) -> list[UnreadPublicationItem]:
|
|
rows = await list_for_user(
|
|
db_session,
|
|
user_id=user_id,
|
|
mode=MODE_LATEST,
|
|
scholar_profile_id=None,
|
|
limit=limit,
|
|
)
|
|
return [_to_unread_item(row) for row in rows]
|
|
|
|
|
|
def _to_unread_item(row: PublicationListItem) -> UnreadPublicationItem:
|
|
return UnreadPublicationItem(
|
|
publication_id=row.publication_id,
|
|
scholar_profile_id=row.scholar_profile_id,
|
|
scholar_label=row.scholar_label,
|
|
title=row.title,
|
|
year=row.year,
|
|
citation_count=row.citation_count,
|
|
venue_text=row.venue_text,
|
|
pub_url=row.pub_url,
|
|
doi=row.doi,
|
|
pdf_url=row.pdf_url,
|
|
)
|