Skip to content

fix: format AAExt per HGVS spec 21.1.3 (drop embedded aaterm) - #890

Open
developer-rpai wants to merge 5 commits into
biocommons:mainfrom
developer-rpai:fix-804-aaext-format
Open

developer-rpai wants to merge 5 commits into
biocommons:mainfrom
developer-rpai:fix-804-aaext-format

Conversation

@developer-rpai

Copy link
Copy Markdown

Closes #804

Bug

AAExt.format() embedded the raw aaterm string between ext and the length, producing descriptions that violate HGVS nomenclature spec 21.1.3 (https://hgvs-nomenclature.org/stable/recommendations/protein/extension/):

  • N-terminal: PosEdit(Interval(Met1, Met1), AAExt(ref="M", aaterm="LGM", length=-2)) formatted as Met1extLeuGlyMet-2; the spec form is Met1ext-2 (the spec explicitly superseded the legacy p.Met1extMet-5 form with p.Met1ext-5, so aaterm has no slot in the N-terminal syntax).
  • C-terminal: PosEdit(Interval(Ter10, Ter10), AAExt(ref="*", aaterm="LG*", length=2)) formatted as Ter10extLeuGlyTer2; the spec form is Ter10LeuextTer2 (":p.Ter" aa_position aa "extTer" extension_length).

Root cause

AAExt.format() in src/hgvs/edit.py built the string as f"{alt}ext{aaterm}{length}" with no regard for which spec slots aaterm may occupy.

Fix

This is a spec-compliance fix to a core data model's serialization, not a cosmetic tweak. The change is confined to AAExt.format():

  • N-terminal extension (negative length): aaterm is dropped, formatting as {alt}ext{length}. The optional alt prefix is preserved (the grammar still round-trips p.Met1Valext-10).
  • C-terminal extension: the Ter110GlnextTer17 shape is kept. A multi-residue aaterm tail sequence is reduced to its spec slots: the first residue becomes the aa replacing the stop codon (only when alt is unset) and the last residue becomes the new terminator. Single-residue aaterm values (everything the parser itself produces, and what altseq_to_hgvsp constructs) format exactly as before.

Verification

  • New regression test test_AAExt_issue804 in tests/test_hgvs_edit.py uses the issue's exact examples plus the spec's expected forms. It failed on pristine upstream code (Met1extLeuGlyMet-2, Ter10extLeuGlyTer2) and passes with the fix (Met1ext-2, Ter10LeuextTer2).
  • Existing suites covering edit/posedit formatting all pass with no network or UTA: test_hgvs_edit, test_hgvs_posedit, test_hgvs_grammar, test_hgvs_grammar_equivalence, test_hgvs_grammar_full, test_hgvs_parser, test_hgvs_location, test_hgvs_sequencevariant (63 passed, 27 skipped; skips are pre-existing data-hungry/network tests).
  • Parser round-trips verified manually: p.Ter314Trpext*45 -> p.Ter314TrpextTer45, p.Met1ext-12 unchanged, legacy p.Met1extMet-5 now normalizes to the spec form p.Met1ext-5, p.Ter327ArgextTer? unchanged. The p_term_asterisk config path (Leuext*2) and uncertain wrapping ((ext-2)) also verified.

What I could not verify

  • I did not run the UTA/database-backed test suites (no database access in this environment), so any downstream consumer of AAExt formatting that depends on live data (e.g. variantmapper, validator) was not exercised.
  • CI on this PR has not been observed; I am not claiming it is green.

N-terminal extensions now format as Met1ext-5 (aaterm is not part of
the spec syntax; this supersedes the legacy Met1extMet-5 form).
C-terminal extensions keep the Ter110GlnextTer17 shape; a multi-residue
aaterm tail sequence is reduced to the aa replacing the stop codon and
the new terminator.

Closes biocommons#804
@developer-rpai
developer-rpai requested a review from a team as a code owner September 24, 2026 06:08
@github-actions

github-actions Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

This issue is stale because it has been open 180 days with no activity.
Remove stale label or comment or this will be closed in 30 days.

@github-actions github-actions Bot added the stale label Oct 1, 2026
@developer-rpai

developer-rpai commented Oct 2, 2026 •

Copy link
Copy Markdown
Author

@reece gentle ping on this one — it closes #804. AAExt.format() was embedding the raw aaterm string between ext and the length (Met1extLeuGlyMet-2); the fix drops it to the spec 21.1.3 form (Met1ext-2). CI and SonarCloud quality gate are green. Flagging mainly because the stale bot marked it — happy to adjust anything you'd like changed. Thanks!

@github-actions github-actions Bot removed the stale label Oct 3, 2026
@andreasprlic

Copy link
Copy Markdown
Member

Thanks for this contribution. Can we make sure the updated pdots pass a full-circle test also when parsed again as strings? It feels like this is only part of the required change and the parser also needs a corresponding upgrade.

…cle round-trip test

The formatter now emits spec 21.1.3 protein extensions (Met1ext-2,
Ter10LeuextTer2), but the parser rejected the multi-residue aaterm tails
that older formatters produced (e.g. Met1extLeuGlyMet-2, Ter10GlnextLG*17).

Extend aa13_ext in both grammar backends (hgvs.pymeta + regenerated
ometa code, and the parallel pyparsing grammar) with alternatives for
multi-residue tails: aat13_seq + nnum (N-terminal) and aat13_seq + num
(C-terminal, explicit length required so bare extGlyTrp stays invalid).
The formatter's spec-slot reduction normalizes these on output, so
legacy strings now parse and round-trip to the spec form.

Add test_AAExt_issue804_roundtrip: format -> parse -> format fixed-point
checks for the issue's examples, spec forms, and legacy normalizations.
@developer-rpai

Copy link
Copy Markdown
Author

Good catch on both counts. Pushed a second commit (d58fc56) that does exactly that:

  1. Parser upgrade: aa13_ext now accepts multi-residue aaterm tails in both grammar backends (pymeta + the pyparsing grammar), so legacy strings like Met1extLeuGlyMet-2 and Ter10GlnextLG*17 parse again. The formatter's spec-slot reduction normalizes these to the 21.1.3 form on output.

  2. Full-circle test: test_AAExt_issue804_roundtrip does format -> parse -> format fixed-point checks for the issue's examples, the spec forms, and the legacy normalizations.

All edit and parser tests pass locally and the SonarCloud quality gate is green. Let me know if you'd like anything adjusted.

@reece reece left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice work @developer-rpai! Thank you for the contribution!

I made one minor request for change for consistency. Otherwise I think this is ready to merge.

Comment thread tests/test_hgvs_edit.py Outdated
self.assertEqual(str(hgvs.edit.AAExt("A", "V", None, -10).type), "ext")
self.assertEqual(str(hgvs.edit.AAExt("A", None, None, -5).type), "ext")

def test_AAExt_issue804(self):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please move to tests/issues/test_804.py, mostly for consistency with other issue-oriented tests.

reece and others added 2 commits October 11, 2026 22:49
Per review feedback, relocate test_AAExt_issue804 and
test_AAExt_issue804_roundtrip from tests/test_hgvs_edit.py to the
dedicated issue test module, converted to pytest style.
@sonarqubecloud

Copy link
Copy Markdown

This branch has not been deployed

No deployments
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.

Formatting of extensions does not follow spec

3 participants