temp commit
This commit is contained in:
parent
8760f27b51
commit
0e9e49df16
193 changed files with 23228 additions and 935 deletions
|
|
@ -1,32 +1,16 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timezone
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from app.services.domains.publications import pdf_resolution_pipeline as pipeline
|
||||
from app.services.domains.publication_identifiers.types import DisplayIdentifier
|
||||
from app.services.domains.unpaywall.application import OaResolutionOutcome
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class _Candidate:
|
||||
url: str
|
||||
confidence_score: float
|
||||
label_present: bool
|
||||
reason: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class _Candidates:
|
||||
container_seen: bool
|
||||
labeled_candidate: _Candidate | None
|
||||
fallback_candidate: _Candidate | None
|
||||
warnings: tuple[str, ...] = ()
|
||||
|
||||
|
||||
def _row(*, doi: str | None = None) -> SimpleNamespace:
|
||||
def _row(*, display_identifier: DisplayIdentifier | None = None) -> SimpleNamespace:
|
||||
return SimpleNamespace(
|
||||
publication_id=1,
|
||||
scholar_profile_id=1,
|
||||
|
|
@ -36,7 +20,7 @@ def _row(*, doi: str | None = None) -> SimpleNamespace:
|
|||
citation_count=0,
|
||||
venue_text=None,
|
||||
pub_url="https://scholar.google.com/citations?view_op=view_citation&citation_for_view=abc:xyz",
|
||||
doi=doi,
|
||||
display_identifier=display_identifier,
|
||||
pdf_url=None,
|
||||
is_read=False,
|
||||
is_favorite=False,
|
||||
|
|
@ -45,7 +29,20 @@ def _row(*, doi: str | None = None) -> SimpleNamespace:
|
|||
)
|
||||
|
||||
|
||||
def _oa_outcome(*, pdf_url: str | None, source: str = "unpaywall") -> OaResolutionOutcome:
|
||||
def _api_outcome(*, pdf_url: str | None, source: str = "unpaywall") -> OaResolutionOutcome | None:
|
||||
if not pdf_url:
|
||||
return None
|
||||
return OaResolutionOutcome(
|
||||
publication_id=1,
|
||||
doi="10.1000/example",
|
||||
pdf_url=pdf_url,
|
||||
failure_reason=None if pdf_url else "no_pdf_found",
|
||||
source=source,
|
||||
used_crossref=False,
|
||||
)
|
||||
|
||||
|
||||
def _oa_fallback_outcome(*, pdf_url: str | None, source: str = "unpaywall") -> OaResolutionOutcome:
|
||||
return OaResolutionOutcome(
|
||||
publication_id=1,
|
||||
doi="10.1000/example",
|
||||
|
|
@ -57,97 +54,63 @@ def _oa_outcome(*, pdf_url: str | None, source: str = "unpaywall") -> OaResoluti
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pipeline_prefers_labeled_scholar_candidate_before_oa(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
async def _fake_candidates(_url):
|
||||
return _Candidates(
|
||||
container_seen=True,
|
||||
labeled_candidate=_Candidate(
|
||||
url="https://arxiv.org/pdf/1703.06103",
|
||||
confidence_score=0.98,
|
||||
label_present=True,
|
||||
reason="scholar_link_labeled_pdf",
|
||||
),
|
||||
fallback_candidate=None,
|
||||
)
|
||||
async def test_pipeline_prefers_openalex_before_arxiv(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
async def _fake_openalex(row, request_email: str | None = None, openalex_api_key: str | None = None):
|
||||
return _api_outcome(pdf_url="https://oa.example.org/found.pdf", source="openalex")
|
||||
|
||||
async def _fail_oa(*, row, request_email):
|
||||
raise AssertionError(f"OA should not run when labeled Scholar candidate exists: {row.publication_id} {request_email}")
|
||||
async def _fail_arxiv(row):
|
||||
raise AssertionError(f"arXiv should not run when OpenAlex candidate exists.")
|
||||
|
||||
monkeypatch.setattr(pipeline, "fetch_link_candidates_from_scholar_publication_page", _fake_candidates)
|
||||
monkeypatch.setattr(pipeline, "_oa_outcome", _fail_oa)
|
||||
|
||||
result = await pipeline.resolve_publication_pdf_outcome_for_row(row=_row(), request_email="user@example.com")
|
||||
|
||||
assert result.outcome is not None
|
||||
assert result.outcome.pdf_url == "https://arxiv.org/pdf/1703.06103"
|
||||
assert result.outcome.source == pipeline.PDF_SOURCE_SCHOLAR_PUBLICATION_PAGE
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pipeline_uses_oa_result_before_unlabeled_fallback(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
async def _fake_candidates(_url):
|
||||
return _Candidates(
|
||||
container_seen=True,
|
||||
labeled_candidate=None,
|
||||
fallback_candidate=_Candidate(
|
||||
url="https://example.org/download/42",
|
||||
confidence_score=0.2,
|
||||
label_present=False,
|
||||
reason="scholar_link_unlabeled_fallback",
|
||||
),
|
||||
)
|
||||
|
||||
async def _fake_oa(*, row, request_email):
|
||||
assert request_email == "user@example.com"
|
||||
return _oa_outcome(pdf_url="https://oa.example.org/found.pdf")
|
||||
|
||||
async def _fail_fallback(*, row, candidate):
|
||||
raise AssertionError(f"Unlabeled fallback should not run when OA returns PDF: {row.publication_id} {candidate.url}")
|
||||
|
||||
monkeypatch.setattr(pipeline, "fetch_link_candidates_from_scholar_publication_page", _fake_candidates)
|
||||
monkeypatch.setattr(pipeline, "_oa_outcome", _fake_oa)
|
||||
monkeypatch.setattr(pipeline, "_unlabeled_fallback_outcome", _fail_fallback)
|
||||
monkeypatch.setattr(pipeline, "_openalex_outcome", _fake_openalex)
|
||||
monkeypatch.setattr(pipeline, "_arxiv_outcome", _fail_arxiv)
|
||||
|
||||
result = await pipeline.resolve_publication_pdf_outcome_for_row(row=_row(), request_email="user@example.com")
|
||||
|
||||
assert result.outcome is not None
|
||||
assert result.outcome.pdf_url == "https://oa.example.org/found.pdf"
|
||||
assert result.outcome.source == "unpaywall"
|
||||
assert result.outcome.source == "openalex"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pipeline_uses_unlabeled_fallback_after_oa_failure(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
fallback_candidate = _Candidate(
|
||||
url="https://example.org/download/42",
|
||||
confidence_score=0.2,
|
||||
label_present=False,
|
||||
reason="scholar_link_unlabeled_fallback",
|
||||
)
|
||||
async def test_pipeline_uses_arxiv_after_openalex_failure(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
async def _fake_openalex(row, request_email: str | None = None, openalex_api_key: str | None = None):
|
||||
return None
|
||||
|
||||
async def _fake_candidates(_url):
|
||||
return _Candidates(container_seen=True, labeled_candidate=None, fallback_candidate=fallback_candidate)
|
||||
async def _fake_arxiv(row, request_email: str | None = None):
|
||||
return _api_outcome(pdf_url="https://arxiv.org/pdf/1234.5678.pdf", source="arxiv")
|
||||
|
||||
async def _fail_oa(*, row, request_email):
|
||||
raise AssertionError("Unpaywall should not run when arXiv returns PDF.")
|
||||
|
||||
monkeypatch.setattr(pipeline, "_openalex_outcome", _fake_openalex)
|
||||
monkeypatch.setattr(pipeline, "_arxiv_outcome", _fake_arxiv)
|
||||
monkeypatch.setattr(pipeline, "_oa_outcome", _fail_oa)
|
||||
|
||||
result = await pipeline.resolve_publication_pdf_outcome_for_row(row=_row(), request_email="user@example.com")
|
||||
|
||||
assert result.outcome is not None
|
||||
assert result.outcome.pdf_url == "https://arxiv.org/pdf/1234.5678.pdf"
|
||||
assert result.outcome.source == "arxiv"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pipeline_uses_unpaywall_after_arxiv_failure(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
async def _fake_openalex(row, request_email: str | None = None, openalex_api_key: str | None = None):
|
||||
return None
|
||||
|
||||
async def _fake_arxiv(row, request_email: str | None = None):
|
||||
return None
|
||||
|
||||
async def _fake_oa(*, row, request_email):
|
||||
assert request_email == "user@example.com"
|
||||
return _oa_outcome(pdf_url=None)
|
||||
return _oa_fallback_outcome(pdf_url="https://example.org/fallback.pdf", source="unpaywall")
|
||||
|
||||
async def _fake_fallback(*, row, candidate):
|
||||
assert candidate == fallback_candidate
|
||||
return OaResolutionOutcome(
|
||||
publication_id=row.publication_id,
|
||||
doi=row.doi,
|
||||
pdf_url="https://example.org/fallback.pdf",
|
||||
failure_reason=None,
|
||||
source=pipeline.PDF_SOURCE_SCHOLAR_PUBLICATION_PAGE_UNLABELED,
|
||||
used_crossref=False,
|
||||
)
|
||||
|
||||
monkeypatch.setattr(pipeline, "fetch_link_candidates_from_scholar_publication_page", _fake_candidates)
|
||||
monkeypatch.setattr(pipeline, "_openalex_outcome", _fake_openalex)
|
||||
monkeypatch.setattr(pipeline, "_arxiv_outcome", _fake_arxiv)
|
||||
monkeypatch.setattr(pipeline, "_oa_outcome", _fake_oa)
|
||||
monkeypatch.setattr(pipeline, "_unlabeled_fallback_outcome", _fake_fallback)
|
||||
|
||||
result = await pipeline.resolve_publication_pdf_outcome_for_row(row=_row(), request_email="user@example.com")
|
||||
|
||||
assert result.outcome is not None
|
||||
assert result.outcome.pdf_url == "https://example.org/fallback.pdf"
|
||||
assert result.outcome.source == pipeline.PDF_SOURCE_SCHOLAR_PUBLICATION_PAGE_UNLABELED
|
||||
assert result.outcome.source == "unpaywall"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue