Intermediate commit
This commit is contained in:
parent
0e9e49df16
commit
3d4cfeff1a
65 changed files with 5507 additions and 333 deletions
|
|
@ -1,5 +1,6 @@
|
|||
from app.services.domains.publication_identifiers.application import (
|
||||
DisplayIdentifier,
|
||||
display_identifier_for_publication_id,
|
||||
derive_display_identifier_from_values,
|
||||
overlay_pdf_queue_items_with_display_identifiers,
|
||||
overlay_publication_items_with_display_identifiers,
|
||||
|
|
@ -9,6 +10,7 @@ from app.services.domains.publication_identifiers.application import (
|
|||
|
||||
__all__ = [
|
||||
"DisplayIdentifier",
|
||||
"display_identifier_for_publication_id",
|
||||
"derive_display_identifier_from_values",
|
||||
"overlay_pdf_queue_items_with_display_identifiers",
|
||||
"overlay_publication_items_with_display_identifiers",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from sqlalchemy import select
|
|||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.db.models import Publication, PublicationIdentifier
|
||||
from app.services.domains.arxiv.guards import arxiv_skip_reason_for_item
|
||||
from app.services.domains.doi.normalize import normalize_doi
|
||||
from app.services.domains.publication_identifiers.normalize import (
|
||||
normalize_arxiv_id,
|
||||
|
|
@ -124,19 +125,45 @@ async def discover_and_sync_identifiers_for_publication(
|
|||
scholar_label: str,
|
||||
) -> None:
|
||||
await sync_identifiers_for_publication_fields(db_session, publication=publication)
|
||||
|
||||
existing_doi = await _existing_identifier_by_kind(
|
||||
|
||||
publication_id = int(publication.id)
|
||||
if await _has_confident_identifier(
|
||||
db_session,
|
||||
publication_id=int(publication.id),
|
||||
publication_id=publication_id,
|
||||
kind=IdentifierKind.DOI.value,
|
||||
)
|
||||
if existing_doi is not None:
|
||||
confidence_floor=0.0,
|
||||
):
|
||||
return
|
||||
|
||||
from app.services.domains.crossref import application as crossref_service
|
||||
item = _identifier_lookup_item(publication=publication, scholar_label=scholar_label)
|
||||
has_strong_doi = await _discover_crossref_doi(
|
||||
db_session,
|
||||
publication_id=publication_id,
|
||||
item=item,
|
||||
)
|
||||
existing_arxiv = await _existing_identifier_by_kind(
|
||||
db_session,
|
||||
publication_id=publication_id,
|
||||
kind=IdentifierKind.ARXIV.value,
|
||||
)
|
||||
skip_reason = arxiv_skip_reason_for_item(
|
||||
item=item,
|
||||
has_strong_doi=has_strong_doi,
|
||||
has_existing_arxiv=existing_arxiv is not None,
|
||||
)
|
||||
if skip_reason is not None:
|
||||
return
|
||||
await _discover_arxiv_identifier(db_session, publication_id=publication_id, item=item)
|
||||
|
||||
|
||||
def _identifier_lookup_item(
|
||||
*,
|
||||
publication: Publication,
|
||||
scholar_label: str,
|
||||
) -> UnreadPublicationItem:
|
||||
from app.services.domains.publications.types import UnreadPublicationItem
|
||||
|
||||
item = UnreadPublicationItem(
|
||||
|
||||
return UnreadPublicationItem(
|
||||
publication_id=int(publication.id),
|
||||
scholar_profile_id=0,
|
||||
scholar_label=scholar_label,
|
||||
|
|
@ -147,41 +174,78 @@ async def discover_and_sync_identifiers_for_publication(
|
|||
pub_url=publication.pub_url,
|
||||
pdf_url=publication.pdf_url,
|
||||
)
|
||||
|
||||
discovered_doi = await crossref_service.discover_doi_for_publication(item=item)
|
||||
if discovered_doi:
|
||||
normalized_doi = normalize_doi(discovered_doi)
|
||||
if normalized_doi:
|
||||
candidate = _candidate(
|
||||
IdentifierKind.DOI,
|
||||
discovered_doi,
|
||||
normalized_doi,
|
||||
"crossref_api",
|
||||
CONFIDENCE_MEDIUM,
|
||||
None,
|
||||
)
|
||||
await _upsert_publication_candidate(db_session, publication_id=int(publication.id), candidate=candidate)
|
||||
|
||||
existing_arxiv = await _existing_identifier_by_kind(
|
||||
db_session,
|
||||
publication_id=int(publication.id),
|
||||
kind=IdentifierKind.ARXIV.value,
|
||||
|
||||
async def _discover_crossref_doi(
|
||||
db_session: AsyncSession,
|
||||
*,
|
||||
publication_id: int,
|
||||
item: UnreadPublicationItem,
|
||||
) -> bool:
|
||||
from app.services.domains.crossref import application as crossref_service
|
||||
|
||||
discovered_doi = await crossref_service.discover_doi_for_publication(item=item)
|
||||
normalized_doi = normalize_doi(discovered_doi)
|
||||
if normalized_doi is None:
|
||||
return False
|
||||
candidate = _candidate(
|
||||
IdentifierKind.DOI,
|
||||
discovered_doi,
|
||||
normalized_doi,
|
||||
"crossref_api",
|
||||
CONFIDENCE_MEDIUM,
|
||||
None,
|
||||
)
|
||||
if existing_arxiv is None:
|
||||
from app.services.domains.arxiv import application as arxiv_service
|
||||
discovered_arxiv = await arxiv_service.discover_arxiv_id_for_publication(item=item)
|
||||
if discovered_arxiv:
|
||||
normalized_arxiv = normalize_arxiv_id(discovered_arxiv)
|
||||
if normalized_arxiv:
|
||||
candidate = _candidate(
|
||||
IdentifierKind.ARXIV,
|
||||
discovered_arxiv,
|
||||
normalized_arxiv,
|
||||
"arxiv_api",
|
||||
CONFIDENCE_MEDIUM,
|
||||
None,
|
||||
)
|
||||
await _upsert_publication_candidate(db_session, publication_id=int(publication.id), candidate=candidate)
|
||||
await _upsert_publication_candidate(
|
||||
db_session,
|
||||
publication_id=publication_id,
|
||||
candidate=candidate,
|
||||
)
|
||||
return candidate.confidence_score >= CONFIDENCE_MEDIUM
|
||||
|
||||
|
||||
async def _discover_arxiv_identifier(
|
||||
db_session: AsyncSession,
|
||||
*,
|
||||
publication_id: int,
|
||||
item: UnreadPublicationItem,
|
||||
) -> None:
|
||||
from app.services.domains.arxiv import application as arxiv_service
|
||||
|
||||
discovered_arxiv = await arxiv_service.discover_arxiv_id_for_publication(item=item)
|
||||
normalized_arxiv = normalize_arxiv_id(discovered_arxiv)
|
||||
if normalized_arxiv is None:
|
||||
return
|
||||
candidate = _candidate(
|
||||
IdentifierKind.ARXIV,
|
||||
discovered_arxiv,
|
||||
normalized_arxiv,
|
||||
"arxiv_api",
|
||||
CONFIDENCE_MEDIUM,
|
||||
None,
|
||||
)
|
||||
await _upsert_publication_candidate(
|
||||
db_session,
|
||||
publication_id=publication_id,
|
||||
candidate=candidate,
|
||||
)
|
||||
|
||||
|
||||
async def _has_confident_identifier(
|
||||
db_session: AsyncSession,
|
||||
*,
|
||||
publication_id: int,
|
||||
kind: str,
|
||||
confidence_floor: float,
|
||||
) -> bool:
|
||||
existing = await _existing_identifier_by_kind(
|
||||
db_session,
|
||||
publication_id=publication_id,
|
||||
kind=kind,
|
||||
)
|
||||
if existing is None:
|
||||
return False
|
||||
return float(existing.confidence_score) >= float(confidence_floor)
|
||||
|
||||
|
||||
def _publication_field_candidates(publication: Publication) -> list[IdentifierCandidate]:
|
||||
|
|
@ -339,6 +403,28 @@ def _overlay_queue_item(
|
|||
return replace(item, display_identifier=fallback)
|
||||
|
||||
|
||||
async def display_identifier_for_publication_id(
|
||||
db_session: AsyncSession,
|
||||
*,
|
||||
publication_id: int,
|
||||
) -> DisplayIdentifier | None:
|
||||
normalized_id = int(publication_id)
|
||||
if normalized_id <= 0:
|
||||
raise ValueError("publication_id must be positive.")
|
||||
mapping = await _display_identifier_map(db_session, publication_ids=[normalized_id])
|
||||
display = mapping.get(normalized_id)
|
||||
if display is not None:
|
||||
return display
|
||||
publication = await db_session.get(Publication, normalized_id)
|
||||
if publication is None:
|
||||
return None
|
||||
return derive_display_identifier_from_values(
|
||||
doi=None,
|
||||
pub_url=publication.pub_url,
|
||||
pdf_url=publication.pdf_url,
|
||||
)
|
||||
|
||||
|
||||
async def _display_identifier_map(
|
||||
db_session: AsyncSession,
|
||||
*,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from app.services.domains.doi.normalize import normalize_doi
|
|||
from app.services.domains.publication_identifiers.types import IdentifierKind
|
||||
|
||||
ARXIV_ABS_RE = re.compile(r"\barxiv:\s*([a-z-]+/\d{7}|\d{4}\.\d{4,5})(v\d+)?\b", re.I)
|
||||
ARXIV_RAW_RE = re.compile(r"^([a-z-]+/\d{7}|\d{4}\.\d{4,5})(v\d+)?$", re.I)
|
||||
ARXIV_PATH_RE = re.compile(r"^/(?:abs|pdf|html|ps|format)/([a-z-]+/\d{7}|\d{4}\.\d{4,5})(v\d+)?(?:\.pdf)?/?$", re.I)
|
||||
PMCID_RE = re.compile(r"\b(PMC\d+)\b", re.I)
|
||||
PUBMED_PATH_RE = re.compile(r"^/(\d+)/?$")
|
||||
|
|
@ -31,6 +32,10 @@ def normalize_arxiv_id(value: str | None) -> str | None:
|
|||
parsed = urlparse(text)
|
||||
if parsed.scheme in {"http", "https"} and "arxiv.org" in parsed.netloc.lower():
|
||||
return _arxiv_from_path(parsed.path)
|
||||
raw_match = ARXIV_RAW_RE.match(text)
|
||||
if raw_match:
|
||||
version = (raw_match.group(2) or "").lower()
|
||||
return f"{raw_match.group(1).lower()}{version}"
|
||||
match = ARXIV_ABS_RE.search(text)
|
||||
if not match:
|
||||
return None
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue