Skip to content

Improve JATS aligner quality for reference segmenter training data#681

Merged
de-code merged 13 commits into
mainfrom
jats-training-data-reference-segmenter
Jul 1, 2026
Merged

Improve JATS aligner quality for reference segmenter training data#681
de-code merged 13 commits into
mainfrom
jats-training-data-reference-segmenter

Conversation

@de-code

@de-code de-code commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

part of https://github.com/eLifePathways/ScienceBeam2.0/issues/110

Expand bibl boundary detection to whole-line level and fix a series of
alignment issues that caused sub-fields to be missed or mis-labelled in
the generated training data:

  • Recover missing scielo bibls with a relaxed parent-match threshold
  • Fix reference label annotation for single-digit and digit-prefixed labels
    (e.g. "1-"), including a pre-buffer restriction to pure-number labels only
  • Fix first-author token missing when consecutive SW reference matches overlap
  • Attach trailing periods that fall outside the outer SW match range
  • Extend the 200-char backward pre-buffer from REFERENCE_AUTHOR to also
    cover REFERENCE_SOURCE, so sources that appear before the parent SW anchor
    in the PDF (e.g. when PDF order is author → source → title but JATS order
    is author → title → source) are not missed
  • Fix chapter-title extraction and page range attributes in citation data

de-code and others added 13 commits June 26, 2026 13:56
Two improvements to JATS-guided training data generation for the
reference-segmenter and citation models:

1. Line-level gap fill in _split_references_by_jats_instance: unlabeled
   lines (e.g. DOI continuation lines that the aligner didn't match)
   inherit the preceding line's REFERENCE instance_id rather than being
   silently dropped. Restores DOI continuation text that was absent from
   citation training data.

2. Whole-line expansion in ReferenceSegmenterModelTrainingDataGenerator
   .get_jats_label_fn: if any token on a PDF line is labeled REFERENCE,
   all tokens on that line receive the same <reference>/<label> tag.
   Eliminates the fragmented <bibl> spans caused by unlabeled separator
   tokens (colons, semicolons, whitespace between sub-fields).

Both changes rest on the invariant that no two distinct references share
a single PDF line, which holds for all observed training documents.
Each reference in the generated TEI now lands in its own <bibl>,
with its label (e.g. "9.") at the start rather than stranded at the
end of the previous reference. This means the reference-segmenter
model trains on correctly structured data, improving its ability to
detect where one reference ends and the next begins — particularly
for references whose number appears on the same line as their content.

Also fixes block splitting for the citation model path: a layout block
that straddles two JATS instances is now split at the boundary rather
than assigned wholesale to one reference, and running headers/page
numbers inside the references section no longer bleed into adjacent
references via gap fill.
  Bracket-style reference labels like `[1]`, `[2]` were not appearing in `<label>` elements in the
  generated TEI training data. The PDF tokeniser splits `[1]` into three tokens `[`, `1`, `]`,
  producing `[ 1 ]` in the alignment haystack. The Smith-Waterman library's traceback logic
  terminates early at gap moves, so it matched only the final `]` (quality 0.33, below threshold).
  A new `_try_bracket_label_match` fallback strips the brackets, locates the inner number via exact
  token match, and extends the range to include the surrounding bracket tokens. It also searches a
  small buffer before the nominal segment start, since the parent SW match often begins at `]` —
  leaving `[` and the number outside the sub-field search range.

  Separately, "DOI:" prefix lines (no annotated tokens) were causing the label function to return
  `None`, which backed the TEI writer up to `<listBibl>` level and produced a spurious `<bibl>` for
  the URL on the next line. Lines with no annotated reference tokens now continue the current bibl
  as `<reference>` when a reference is already open.
  Two fixes reduce the scielo citation score regression introduced when
  JATS-guided bibl splitting replaced ML-model splitting:

  1. field_extractor.py: strip <pub-id> subtrees from the parent
     reference text used for Smith-Waterman alignment.  PMID/PMCID values
     appear in JATS but not in PDF reference lists; including them pads the
     needle length without adding matched characters, pushing quality below
     the 0.8 threshold.

  2. aligner.py: when a REFERENCE parent match fails at 0.8, retry with a
     relaxed 0.65 threshold.  JATS may concatenate author initials ("CA")
     or order publisher/place differently from the PDF, producing valid
     near-threshold alignments that the primary pass rejects.

  Both issues caused parent matches to silently fail, skipping a bibl
  entirely.  Because partial_list scoring is ordered, one skipped bibl
  shifts every subsequent field comparison by one position, cascading into
  many false mismatches (e.g. PPR489375 dropped from 22/22 to 3/22 correct
  issue matches due to a single missing bibl at position 2).

  After these fixes the scielo regression shrinks from −0.124 to −0.009
  mean while ORE remains at +0.363 mean, for a net +0.177 on citation.
…aining data

- Add <chapter-title> to REFERENCE_ARTICLE_TITLE XPath so book-chapter and
  government-document refs are annotated with titles (recovers ~31 missing
  titles across scielo_preprints-jats documents)
- Post-process <biblScope unit="page"> elements to add @from/@to attributes
  when the element contains a fpage-lpage range ("388 - 412"), matching the
  format expected by sciencebeam-judge for correct fpage extraction

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A truncated PDF reference (one where year/volume/URL are absent from the
scanned text) produces a JATS needle much longer than the matching haystack
fragment, giving quality ≈ 0.60.  The previous 0.65 floor rejected these,
leaving the bibl unmatched and cascading into position-shifted comparisons
for every subsequent ordered field (reference_issue, reference_volume).

Lowering to 0.55 captures these cases while still rejecting genuinely
absent references.  When the parent match now succeeds, the aligner can
search for sub-fields beyond the reference-segmenter bibl boundary —
recovering the source annotation and any continuation text the segmenter
missed — producing a complete, separate citation bibl for the reference.

Scielo impact (PPR459390 case):
  reference_issue/exact:   0.943 → 0.987 (+0.044)
  reference_volume/exact:  0.979 → 0.998 (+0.019)
  reference_source/lev:    0.984 → 0.996 (+0.012)
  scielo mean:             0.908 → 0.919 (+0.011)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…arent match

Labels "1."–"9." appear immediately before the JATS parent text in the PDF (the
parent text excludes the label), so the Smith-Waterman parent match starts after
them.  The sub-field search range began at p_start, placing single-digit labels
outside it.

Two-part fix:
- Wire in _SUB_FIELD_PARENT_PRE_BUFFER (was defined but unused) and set it to 20
  chars.  Applied only for REFERENCE_LABEL sub-fields so other sub-fields (e.g.
  country) cannot false-match an identical word in an earlier context.
- Add _is_punct_suffix_token helper so "1." tokenised as a single token passes
  _exact_number_match even though is_token_boundary_after fails for the digit alone.

Improves citation label annotation (labels 1-9 now appear as <note>N</note>) and
reference-segmenter label score (0.870 → 0.964 on scielo_preprints-jats).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
  REFERENCE_LABEL sub-fields with a suffix (e.g. "20-" in scielo_preprints
  references like "1-", "20-") are included in the JATS parent reference text,
  so p_start is already at the label — extending the search backward would let
  "20-" false-match the "20-" fragment inside a preceding DOI like "2020-0248".

  The fix applies the pre-buffer only when the label text is a pure number
  (digits only). Suffixed labels ("1-", "20-") and all other sub-fields keep
  pre=0 and are confined to the parent match range.

  The pre-buffer value stays at 20 to handle cases where the JATS parent text
  starts after a publisher name ("Brasil."), leaving a ~14-char gap between the
  label token and p_start.

  Adds three unit tests covering the positive case, the DOI false-match
  regression, and the long-gap (extra publisher text) scenario.
…tated

  When a PDF tokenises "1-" as two tokens "1" and "-", Smith-Waterman
  cannot match the 2-char needle "1-" against the spaced haystack "1 - …"
  with sufficient quality (matched=1/2=0.5 < threshold=0.8).  The parent
  SW match compounds the issue: it aligns the full reference text by
  skipping the leading digit and starting at "-" (p_start=2), so the
  label sub-field search range excluded position 0 even for pure-digit
  prefixes of suffixed labels.

  Two targeted fixes in aligner.py:

  1. _search_range: apply a small pre-buffer (_SUB_FIELD_LABEL_DIGIT_PRE_BUFFER=3)
     for any REFERENCE_LABEL whose text starts with digits followed by a
     suffix (e.g. "1-", "9-").  This shifts search_start back to 0 so the
     digit token is within range.

  2. _fuzzy_match_field_value: add _try_numeric_prefix_label_match as a
     final fallback for REFERENCE_LABEL sub-fields when SW fails.  It
     splits the label into a digit prefix and suffix, locates the prefix
     via _exact_number_match (which enforces token-start boundaries and
     therefore cannot false-match inside "2020" or "0248"), then extends
     the match to cover the immediately adjacent suffix token(s).

  Two-digit suffixed labels (e.g. "10-") continue to be found via the
  normal SW path (quality=1.0) and are unaffected.  The existing
  regression guard for "20-" not matching "2020-0248" is preserved.
…overlap

  When _extend_match_for_needle_tail greedily extends a reference's parent
  match into the first token of the next reference, prev_parent_end can
  exceed the current reference's parent start (a_start). Without a guard,
  effective_prev_end is set past the token boundary, causing
  _label_tokens_for_blocks to skip it as a mid-token start.

  Apply the same <= a_start two-column guard to prev_parent_end that already
  existed for prev_id_end.
  When the gap between the last author initial and the next SW match
  point exceeds the fill threshold, the abbreviation period immediately
  following that initial has no entry in token_label_by_id. The
  trailing-period pass previously required entry is not None, so it
  silently skipped the period. Relaxing the condition to also handle
  entry is None attaches it correctly.
  When a PDF orders a reference as author → source → article-title but the
  JATS element order is author → article-title → source, Smith-Waterman
  anchors the parent match on the article-title. The resulting p_start lands
  after the source in the haystack, so the source sub-field search (started
  at p_start) misses it entirely.

  The fix adds REFERENCE_SOURCE to the elif branch that sets the 200-char
  backward pre-buffer, already used by REFERENCE_AUTHOR. Both sub-fields
  share the same pre_parent_ref_floor guard that prevents the extended search
  from reaching into a preceding reference's matched range.
@de-code de-code self-assigned this Jul 1, 2026
@de-code de-code marked this pull request as ready for review July 1, 2026 17:41
@de-code de-code enabled auto-merge (squash) July 1, 2026 17:42
@de-code de-code merged commit 9e517bb into main Jul 1, 2026
6 checks passed
@de-code de-code deleted the jats-training-data-reference-segmenter branch July 1, 2026 17:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant