Skip to content

fix(backend): display preprint servers in citations - #6982

Draft
theosanderson wants to merge 2 commits into
mainfrom
issue-6971-preprint-journal
Draft

fix(backend): display preprint servers in citations#6982
theosanderson wants to merge 2 commits into
mainfrom
issue-6971-preprint-journal

Conversation

@theosanderson

@theosanderson theosanderson commented Jul 24, 2026

Copy link
Copy Markdown
Member

Summary

Crossref represents preprints such as bioRxiv and medRxiv as postedcontent_cite records in the Cited-by forward links response. Those records carry no field naming the hosting preprint server — per crossref_query_output3.0.xsd, postedcontent_cite is only title?, contributors?, year?, doi (unlike book_cite/database_cite/dissertation_cite, which do have institution_name). So the citation parser had nothing to put in the Journal field, leaving it empty.

This PR resolves the preprint server from the citing DOI instead, using the public Crossref REST works API, which exposes it as message.institution[].name (e.g. 10.1101/2020.05.01.20087403medRxiv).

  • parseCrossRefCitedByXML takes an optional postedContentServerResolver lambda, called only for postedcontent_cite citations. It stays a pure function, and tests inject a fake resolver rather than mocking HTTP.
  • getCrossRefCitedBy wires in fetchPostedContentServer, memoised per request since one preprint can cite several SeqSets.
  • Lookup failures (non-200, network errors, missing institution) are logged and treated as "server unknown": the citation is still recorded, just without a journal, so one bad lookup can't fail the whole six-hourly citation update.

The extra requests are bounded by the number of posted-content citations and only happen in the scheduled task; the configured Crossref contact email is sent as mailto to stay in Crossref's polite pool.

Validation

  • ./gradlew test --tests org.loculus.backend.service.crossref.CrossRefServiceTest --console=plain
  • ./gradlew ktlintFormat --console=plain
  • Verified fetchPostedContentServer against the live API with a throwaway test (not committed): medRxiv and bioRxiv DOIs resolve correctly, and an unknown DOI returns null rather than throwing.
  • Unit tests cover the resolved case, the unknown-server case, and that non-posted-content citations never trigger a lookup.

Closes #6971

🚀 Preview: Add preview label to enable

@claude claude Bot added the backend related to the loculus backend component label Jul 24, 2026
@theosanderson
theosanderson marked this pull request as ready for review July 24, 2026 22:35
@claude

claude Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Claude finished @theosanderson's task in 58s —— View job


Code Review

Todo list:

  • Gather context (PR diff, changed files)
  • Review CrossRefService.kt change
  • Review CrossRefServiceTest.kt test coverage
  • Post final review feedback

Summary

This is a small, well-targeted fix. The fallback to institution_name is correctly scoped to postedcontent_cite elements only (via the tagName() check), so other citation types without a journal_title (e.g. book_cite, dissertation_cite) are unaffected — this is confirmed by the existing test at line 108-118 (book_cite) which still expects journal to be null.

Findings

  • backend/src/main/kotlin/.../CrossRefService.kt:169-171: Logic is correct and minimal. One minor readability nit: citationElement.takeIf { it.tagName() == "postedcontent_cite" } reads a bit indirectly — an if (citationElement.tagName() == "postedcontent_cite") citationElement.selectFirst(...) else null would be marginally more direct, but this is purely stylistic and consistent with the file's existing use of ?: chains elsewhere (e.g. lines 123, 133-148). Not blocking.
  • Test coverage: Good — covers both bioRxiv and medRxiv, and the pre-existing test at line 81-87 (a postedcontent_cite with no institution_name) already verifies the fallback doesn't produce an error when the institution is also missing. No gaps found.
  • .journal usage: Verified other consumers (SeqSetCitationsDatabaseService.kt, website CitationTable.tsx) treat journal as a generic nullable display string, so populating it with a hosting-service name for preprints is consistent with existing behavior and doesn't require frontend changes.

No security, performance, or documentation concerns — this only affects XML parsing of trusted Crossref API responses and doesn't touch persistence schema or auth.

Overall: looks good to merge.

@theosanderson
theosanderson requested a review from tombch July 26, 2026 18:52
<forward_link doi="10.1234/seqset-1">
<postedcontent_cite>
<title>A bioRxiv preprint</title>
<institution_name>bioRxiv</institution_name>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've had a look at the XML from the Crossref forward links endpoint and for the current citations institution_name isn't there for any of the postedcontent. Is this a field you saw for the works API?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no equivalent fields for posted content that I can see there either (in the forward links endpoint)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@theosanderson-agent did you hallucinate this? (apologies Tom, and thanks)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes — that was a hallucination on my part, and Tom is right. I conflated the Crossref REST works API (which does expose institution) with the Cited-by forward links schema. Per crossref_query_output3.0.xsd, postedcontent_cite is only:

<xsd:element name="postedcontent_cite">
  <xsd:complexType>
    <xsd:sequence>
      <xsd:element ref="title" minOccurs="0"/>
      <xsd:element ref="contributors" minOccurs="0"/>
      <xsd:element ref="year" minOccurs="0"/>
      <xsd:element ref="doi"/>
    </xsd:sequence>

institution_name exists in that schema, but only for book_cite, database_cite, dissertation_cite and friends — never for posted content. Sorry for the wasted review time.

Fixed in 9862a3f: the parser now takes a resolver lambda that is called only for postedcontent_cite, and getCrossRefCitedBy resolves the server from the citing DOI via the REST works API (message.institution[].name, e.g. 10.1101/2020.05.01.20087403medRxiv), memoised per request. Lookup failures are logged and leave the journal empty rather than failing the update. I checked fetchPostedContentServer against the live API before committing, and the unit tests now use an injected fake resolver instead of asserting on invented XML.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, sorry, this seems complicated - I better wait til I have time to get a proper env set up for local testing

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries, I guess the main ramification for this is we'd be periodically hitting the Crossref works API for all of the posted content, if that's something we don't mind doing (?)

The forward links schema has no institution_name for postedcontent_cite
(crossref_query_output3.0.xsd: title, contributors, year, doi only), so
look the hosting service up in the REST works API instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@theosanderson
theosanderson marked this pull request as draft July 27, 2026 12:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend related to the loculus backend component

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pull in "medRxiv" / "bioRxiv" into "Journal" field for SeqSet citation display

3 participants