Skip to content

Release 2026.3.0 - #907

Merged
bencap merged 280 commits into
mainfrom
release-2026.3.0
Oct 15, 2026
Merged

bencap merged 280 commits into
mainfrom
release-2026.3.0

Conversation

@bencap

@bencap bencap commented Sep 30, 2026

Copy link
Copy Markdown
Collaborator

Features

Bug Fixes

Maintenance

Breaking API changes

  • POST /variants/clingen-allele-id-lookups removed (405).
  • GET /score-sets/{urn}/mapped-variants returns 410; use GET /score-sets/{urn}/variant-details (NDJSON with preMapped/postMapped).
  • GET /mapped-variants/{urn} and /mapped-variants/vrs/{id} return 301 to /variants/... with a new body shape; /mapped-variants/{urn}/va/* returns 301 and is wire-compatible.
  • GET /variants/{urn} returns VariantDetail. id, data, hgvsNt/hgvsPro/hgvsSplice, scoreSet, scoreSetId, mappedVariants[], the dates and recordType are gone.
  • Clinical controls mappedVariants → clinvarLinks[{variantUrn, alleleDigest}]; gnomAD variants mappedVariants → variantLinks[{variantUrn, alleleDigest}].
  • Removed schemas: MappedVariant*, TargetGeneMapping, AnnotationLayer, Variant, VariantEffectMeasurementWith*ScoreSet, ClingenAlleleIdVariant*.
  • only_current is replaced by as_of. CSV rows are ordered by variant number, and gnomAD columns are no longer filtered to one db_version.
  • New routes: GET /alleles/{identifier}, /clingen-alleles/{id}/measurements, /score-sets/{urn}/variants, /score-sets/{urn}/variant-details, /variants/vrs/{identifier}, /variants/{urn}/va/*, /diseases/search.

Deployment

  • Alembic is one linear chain: 27 revisions above main's head 4dbf24ed1857.
  • Build the variants (id, scoreset_id) unique index by hand before deploying; otherwise 45e8ec1db15e builds it inline and blocks writes to variants. 45e8ec1db15e also rewrites mapping_records under an exclusive lock (~4–5 min per million rows).
  • a7c3e5f9b1d2 builds uq_alleles_hgvs concurrently. Run the duplicate check in its docstring first.

bencap added 30 commits July 9, 2026 12:46
get_hgvs_from_post_mapped can now join the members of a multi-variant
block (Haplotype/CisPhasedBlock) into a single bracketed cis-phased
expression via the new combine_cis flag, replacing the previous
behavior of returning None for any multi-variant block.

- combine_cis defaults off because some consumers cannot yet handle a
  bracketed expression — notably ClinGen submission, which has no
  single CAID for a multi-variant cis block (#764)
- the CSV export fallback opts in, so g./p. HGVS columns are populated
  from cis-phased post-mapped output instead of left empty
- drop the stale commented-out error branches and the resolved TODO
…-keyed refresh

Replace MappedVariant-based gnomAD linkage with valid-time GnomadAlleleLink rows
keyed on the deduplicated Allele. Linking covers every current allele of a score
set (authoritative and RT-derived), so protein/coding score sets — whose genomic
allele is RT-derived — are no longer dropped; per-variant annotation status flows
through the _annotate_gnomad choke point against authoritative links only (interim
bandaid, the AnnotationEvent migration seam).

Refresh / idempotency model:
- One live link per allele (unique index on allele_id); a gnomAD version bump
  supersedes rather than accumulating one live link per version.
- Supersede only on change — an unchanged re-run leaves the live link untouched,
  so the valid-time history records no spurious boundary.
- Version-keyed skip avoids re-fetching alleles already current at the version; a
  force param bypasses the skip (re-ingestion / heal) without churning unchanged links.
- Per-variant status is a per-run audit event: created / preexisting / skipped.

Normalize CAIDs across the Athena join: the gnomAD Hail dump drops leading zeros
(CA025094 -> CA25094), so exact-string matching silently missed zero-padded CAIDs.

Extract group_alleles_for_annotation as the shared allele-grouping primitive for
allele-subject annotation jobs (adopted by gnomAD; VEP/ClinVar to follow).

Refs #742. Partially addresses #722 (CAID-completeness tracking there stays open).
Migrate the VEP functional-consequence job (Step 2 of the annotation
infrastructure migration) off MappedVariant onto the deduplicated allele
model.

- New ValidTime vep_allele_consequences table: a single allele-keyed row
  collapses record + link (the consequence is a scalar with no shared
  external entity, unlike gnomAD/ClinVar). One live consequence per allele
  via a partial unique index.
- Job runs over the score set's full allele set (authoritative + RT-derived)
  via get_alleles_for_score_set + the shared grouping primitive; VAS still
  fans only to authoritative variants (the bandaid seam).
- Version-key the refresh on the Ensembl release (/info/software): skip
  alleles already current, abort if the release can't be fetched. Supersede
  is value-keyed, not version-keyed — an unchanged consequence at a new
  release bumps source_version/access_date in place to avoid churning
  history. force bypasses the skip (e.g. after editing VEP_CONSEQUENCES).
- A no-result is treated as a non-answer, never a negative: held
  consequences are not retired on an empty/failed VEP fetch.
- Annotation links stay one-directional to Allele (no reverse back-ref).

Adds lib + job tests covering linkage, RT-derived scope, version skip,
in-place bump, supersede-on-change, no-result handling, and release-fetch
failure.
Step 3 of the #742 external-annotation migration. ClinVar linkage moves
off MappedVariant onto the deduplicated allele model.

- Rename clinical_controls -> clinvar_controls (ORM model + table +
  unique constraint). Internal only: the ClinicalControl* view models,
  the /clinical-controls serving endpoints, and the frozen
  mapped_variants_clinical_controls association are unchanged, since the
  view-model name is both the OpenAPI schema name and the record_type
  discriminator consumed by the UI.
- New clinvar_allele_links ValidTime table + ClinvarAlleleLink model.
  Multi-live: partial unique index (allele_id, clinvar_control_id) WHERE
  valid_to IS NULL, so an allele accumulates one live link per release.
- Refactor refresh_clinvar_controls onto get_alleles_for_score_set +
  group_alleles_for_annotation (payload = CAID, full allele scope). Links
  are get-or-create; a same-version re-resolution to a different control
  supersedes newest-wins (gap-free retire+insert) rather than leaving two
  live links. VAS writes funnel through the _annotate_clinvar choke point,
  fanned only to authoritative_variant_ids and version-scoped.
- Additively capture ClinVar's VariationID: nullable clinvar_variation_id
  column populated forward from the variant_summary TSV (parse degrades to
  None on archival schemas lacking the column). Unserved; the dedicated
  clinvar_variants remodel is deferred to the read-cutover.

Tests rewritten for the allele model, covering the multi-live link writes,
the version-scoped supersede guard, and the authoritative-only VAS fan-out.
Steps 4 & 5 of the #742 migration. Both jobs are redundant under the allele model:
Allele.hgvs_g/c/p are populated by the mapping job, and the reverse-translation
equivalence space (genomic/coding/protein alleles linked per MappingRecord) replaces
the ClinGen PA<->CA translation table.

- Remove populate_hgvs_for_score_set and populate_variant_translations_for_score_set
  from the pipeline DAG (both were leaf nodes — no dependency edges to repair), the
  worker registry (BACKGROUND_FUNCTIONS + STANDALONE_JOB_DEFINITIONS), and the
  external_services package exports.
- Delete the two job modules and the now-orphaned ClinGen HGVS helpers
  (extract_hgvs_from_ca_allele_data / extract_hgvs_from_pa_allele_data), used only by
  the HGVS job.
- Keep lib/variant_translations.py and the variant_translations table/model, marked
  FROZEN (serving-only) — they back old-model serving and are dropped at read-cutover.
- Delete the obsolete job tests and their conftest fixtures.
get_allele_translations(db, allele_id, *, as_of=None) returns an allele's full
cross-layer equivalence set (genomic/coding/protein) by traversing the
MappingRecordAllele link graph: allele -> its live links -> mapping record(s) ->
all co-linked alleles.

The relation is co-membership in a MappingRecord's allele set, not a shared
identifier — ClinGen's CAID spans only the nucleotide layers (the protein allele
carries a distinct PA), so the link graph is the only thing tying all layers
together. This replaces what the retired variant_translations PA<->CA table provided.

Forward-compatible with temporal reads: the same half-open valid-time predicate is
applied at both the anchor and fan-out hops, so passing as_of reconstructs the
equivalence set as of any instant. Defaults to the currently-live set.
…bulary

Introduces the AnnotationEvent log: a single append-only event table whose
subjects are Variant and Allele, selected by annotation_type via a polymorphic
CHECK. "Current" is derived (DISTINCT ON … id DESC), never stored. Adds the
shared Disposition (present/absent/not_applicable/failed) and EventReason
vocabulary, the v_current_annotation_events view, and the supporting migrations.
group_alleles_for_annotation now returns one job-specific payload per allele
instead of an AlleleAnnotationGroup. Drops the authoritative_variant_ids bandaid
seam (and is_authoritative from ScoreSetAlleleRow): annotation is an allele-level
fact, and with the AnnotationEvent log the per-variant status fan-out goes away.
AnnotationStatusManager now writes AnnotationEvent rows via record_event and
derives current status from the event log instead of maintaining per-variant
status rows. Resolves subjects through VARIANT_SUBJECT_TYPES/ALLELE_SUBJECT_TYPES
and the Disposition vocabulary.
link_gnomad_variants_to_alleles now returns a GnomadLinkVerdict per touched allele
(CREATED/UNCHANGED) as the single source of truth for per-allele status; the job
derives annotation events directly from the verdict map rather than re-querying
link state. Alleles absent from the map are read as 'gnomAD had no record'.
VEP resolution now returns a VepResolution (consequences vs errored) so a genuine
empty is distinguished from an unknown, and linking returns a VepLinkVerdict per
allele (CREATED/UNCHANGED). The job derives annotation events from these outcomes
instead of re-querying consequence state; errored HGVS are retried, not recorded
as negatives.
The ClinGen allele-registration and LDH-submission jobs now record AnnotationEvents
via record_event, using the shared EventReason vocabulary (created/preexisting/
reconfirmed/submitted, and the CAR-specific failure and reconfirmation-skipped
reasons) in place of per-variant status updates.
The ClinVar job records AnnotationEvents via record_event, distinguishing
superseded/no_record/no_caid/multi_variant_caid cases through the shared
EventReason vocabulary. Clarifies the ClinicalControl comments to mark the
row-level vs variation-level link identifiers.
The mapping job records AnnotationEvents via record_event, deriving disposition
and reason from the typed MappingOutcome (mapped / intronic /
no_protein_consequence / failed) rather than writing per-variant status rows.
The reverse-translation job records AnnotationEvents via record_event, using the
RT-specific EventReason cases (translated / no_coding_transcript /
no_assay_level_hgvs / translation_failed / translation_error /
transcript_unresolved) to distinguish informative negatives from failures.
Adds the pipeline_tracking script for querying pipeline progress from the
AnnotationEvent log, and updates the variant_annotations script to read/write
through the event model.
An allele with an existing CAID always matched the preexisting branch first, so
force_reregister never re-submitted it. Existing-CAID alleles now route to pending
whenever force_reregister is set.
Split the resolution exception handling: an HTTPError (VEP rejecting a batch, e.g.
a 400 for protein HGVS it cannot parse) now routes the batch to Variant Recoder,
which may still resolve it via genomic recoding. Only transport/network errors
(RequestException) are marked errored and held for retry — fixing the case where
recoverable inputs were wrongly recorded as unknown.
normalize_and_identify/_rle_to_lse assume an inlined SequenceReference and integer coordinates, but the VRS models type location.sequenceReference and location.start/rle.length as unions (iriReference/Range/None). Add narrowing assertions documenting that contract — restoring a green `mypy src/` (a required CI job) and turning the unreachable bad-input case into a clear AssertionError instead of a deeper AttributeError. Adds guard tests.
The newest-wins ClinvarAlleleLink supersede stamped valid_to/valid_from with a tz-naive datetime.now() into timestamptz columns, skewing the valid-time axis against every other job's func.now()-stamped rows. Use supersede_with so retire and insert share one DB timestamp. Test now asserts the boundary is timezone-aware.
Every other FK on annotation_event was indexed except score_set_id. Add it (model __table_args__ + migration) to back the ON DELETE SET NULL cascade on score-set deletion and score-set-scoped audit queries.
Revision a1b2c3d4e5f6 creates alleles/mapping_records/mapping_record_alleles — there is no allele-closure table anywhere (equivalence is a runtime query). Rename the file so it stops misleading readers; the revision id is unchanged.
Re-add cases dropped in the manager rewrite: None/empty reads, no-op flush, auto-flush-before-read for both query methods, a FAILED-disposition round-trip through the manager, and per-subject current-status isolation.
…ents

split_cis_phased_hgvs raised ValueError on bracketed input lacking an accession; it now degrades to a single-element list. join_cis_phased_hgvs emits components in coordinate order so CSV exports are deterministic regardless of VRS member ordering (the block digest is order-independent).
get_hgvs_from_post_mapped can now join the members of a multi-variant
block (Haplotype/CisPhasedBlock) into a single bracketed cis-phased
expression via the new combine_cis flag, replacing the previous
behavior of returning None for any multi-variant block.

- combine_cis defaults off because some consumers cannot yet handle a
  bracketed expression — notably ClinGen submission, which has no
  single CAID for a multi-variant cis block (#764)
- the CSV export fallback opts in, so g./p. HGVS columns are populated
  from cis-phased post-mapped output instead of left empty
- drop the stale commented-out error branches and the resolved TODO
Step 3 of the #742 external-annotation migration. ClinVar linkage moves
off MappedVariant onto the deduplicated allele model.

- Rename clinical_controls -> clinvar_controls (ORM model + table +
  unique constraint). Internal only: the ClinicalControl* view models,
  the /clinical-controls serving endpoints, and the frozen
  mapped_variants_clinical_controls association are unchanged, since the
  view-model name is both the OpenAPI schema name and the record_type
  discriminator consumed by the UI.
- New clinvar_allele_links ValidTime table + ClinvarAlleleLink model.
  Multi-live: partial unique index (allele_id, clinvar_control_id) WHERE
  valid_to IS NULL, so an allele accumulates one live link per release.
- Refactor refresh_clinvar_controls onto get_alleles_for_score_set +
  group_alleles_for_annotation (payload = CAID, full allele scope). Links
  are get-or-create; a same-version re-resolution to a different control
  supersedes newest-wins (gap-free retire+insert) rather than leaving two
  live links. VAS writes funnel through the _annotate_clinvar choke point,
  fanned only to authoritative_variant_ids and version-scoped.
- Additively capture ClinVar's VariationID: nullable clinvar_variation_id
  column populated forward from the variant_summary TSV (parse degrades to
  None on archival schemas lacking the column). Unserved; the dedicated
  clinvar_variants remodel is deferred to the read-cutover.

Tests rewritten for the allele model, covering the multi-live link writes,
the version-scoped supersede guard, and the authoritative-only VAS fan-out.
… allele links

Add the Cat-VRS transit foundation for the #743 variant + annotation API. The
CategoricalVariant served on /variants/{urn} is assembled per request from a
variant's live MappingRecordAllele links, not materialized: a stored skeleton on
MappingRecord (the design's D33) would be a denormalized cache of the link graph
with a sync cost and no benefit, since the link graph already derives it on demand
and full-scan paths never assemble Cat-VRS. This reverses D33.

- lib/alleles.py: get_live_record_allele_links — record-scoped live link set for a
  variant (defining + members, as_of-aware), distinct from the cross-record union
  of get_allele_translations.
- lib/cat_vrs.py: build_categorical_variant (pure over links) -> CategoricalVariantTransit
  (spec-pure CategoricalVariant + mode + per-member digest->relation map), plus the
  categorical_variant_for_variant DB wrapper that threads as_of through the fetch.
- ga4gh-cat-vrs promoted to a direct dependency (package 0.7.2 implements Cat-VRS
  spec 1.0.0); members are bare variations with relations on DefiningAlleleConstraint.
- Anomaly logging when an authoritative or member allele has no post_mapped.
- 16 tests (7 unit / 9 integration), mypy + ruff clean.
Add score_from_variant_data / variant_score to lib/variants.py as the single accessor for a variant's score_data.score — float-coercing (accepts numeric strings, rejects bool/None), robust to malformed JSONB. Replaces scattered variant.data["score_data"]["score"] reach-ins, several of which carried a # type: ignore.

Migrate score_calibrations, annotation/util (variant_can_be_annotated), and annotation/study_result to the helper — dropping the duplicated defensive dict/float handling and the type: ignore suppressions. Unit-tested over the coercion / NA / non-dict matrix.
…variants)

Add the lean per-variant dataset the score-set page composes its table, heatmap, and histograms from: variant_urn, score, representative VEP consequence, ClinGen id + assay-level digest, and HGVS in both frames — submitted hgvs_nt/pro/splice (target) and mapped assay_level_hgvs + protein_level_hgvs (reference). Each slot carries the canonical HGVS string plus a parsed {position,ref,alt} block when it is a simple substitution (splice/indels/multivariants keep the string alone).

Assembled in one bulk query, one row per variant: the authoritative allele is joined directly for digest/ClinGen/consequence; the protein HGVS comes from a correlated scalar subquery so there is no allele fan-out. Opt-in ?as_of= reconstructs the annotation layer over the immutable scores/submitted HGVS and is echoed via the X-As-Of header. response_model_exclude_none keeps the payload lean; READ-gated via fetch_score_set_by_urn.

Adds simple-substitution HGVS parsers to lib/hgvs.py. A single canonical coding projection for genomic/coding assays is deferred to #784 (the degenerate reverse-translation set has no canonical member): the hover tooltip uses this strict model, the selection tooltip uses the detail endpoint's full set.

Tests: parser units + lib integration (nt/protein assays, as_of reconstruction, unmapped, unparseable-string, ordering) + router (serialization, exclude_none, X-As-Of, and the READ permission matrix).
bencap and others added 27 commits October 8, 2026 13:06
…nly calls

Score sets store a 24-bin summary of their scores in a new nullable
score_distribution column, written by the variant creation job in the same
transaction as the variants and served as scoreDistribution on the ScoreSet
view model. Bins span the full [min, max] of the numeric scores; null and
non-finite scores are counted separately. backfill_score_distributions fills
existing score sets after migration b4d8f2a6c1e3.

Allele measurements carry a single classification instead of separate
preferred and research-use-only fields. A research-use-only call stands in only
when the request sets include_research_use_only and the score set has no
readable non-RUO calibration with functional classifications;
classificationIsResearchUseOnly marks it. Ordering uses non-RUO calls alone.
Run with --commit against a serving database, a single end-of-run commit held
row locks on every updated score set for the whole run. Each score set now
commits as it finishes, so locks are brief and an interrupted run keeps its
progress. --dry-run still rolls everything back.
…AndThresholdSource

Fix the outer source not found error in both Method sources and Threshold sources in creating a new score set and calibration.
A rerun renumbered projection groups by list position, so a measured
allele's group could change while its link stayed live, pairing it with
the wrong projection. Groups that fold in the measured allele keep their
prior numbers where possible, and a measured-allele link whose group does
change is superseded and re-created under the same timestamp. A fold-in
that misses the measured allele is now logged, counted, and recorded as
fold_in_missed in the candidates metadata.
observe_statements (formerly log_slow_statements) also increments
db_statement_count in the request logging context, so a route's query
count shows up in the request log. CSV tests count statements through the
shared captured_statements helper.
…rs with one rule

The variant-details stream, annotation streams, and public exports loaded
each variant's records, links, cross-references, and classifications
separately. They now load per chunk of 500 variants through
with_chunk_inputs, a fixed number of queries per chunk, and the single
variant routes are chunks of one.

Cat-VRS, variant detail, and allele detail each labelled members with
their own rule and disagreed. cat_vrs.member_label is now the one
labeller. A nucleotide member outside the measured allele's projection
group reads as co_encodes/convergent rather than a coordinate
representation, so a fold-in miss no longer serves convergent siblings as
exact matches.

test_query_scaling walks every parameterized GET route and fails when a
route runs more queries on a larger score set than a smaller one.
A cache hit kept the expiry its first writer set, so data a sibling score
set cached nearly a day earlier could expire minutes after the warming job
read it, leaving the downstream jobs a cold cache. The warming job now
restarts each allele's expiry after fetching it.
The first version of a refresh can make thousands of uncached ClinGen
lookups without updating progress, so the stall reaper cancelled it at
30 minutes. Progress is now reported every 500 alleles.
The mapper's BLAT limit is now 20 minutes and a failed parse runs BLAT
twice, so the 30-minute floor cancelled small score sets before the
mapper's own timeout could fire.
…dle findings

Add --collection and --translated-since alongside --score-set on both audit
scripts, so findings left by an older pipeline drop out. Split
measured_allele_outside_projection_group: changes reaching past the codon RT
targeted and reference-identical alleles are now info-level checks, since RT
can never list them. Correct the malformed-group description: groups of one
come from library projection failures, not only failed translations.
A sequence's only correct ga4gh alias is its own digest. Any other makes
refget lookups for it nondeterministic, as the mapper's stop-codon digest
bug did on staging.
Production RDS runs Postgres 15. Both compose files move to postgres:15 on
new volumes, since a 14 data directory won't start under 15; the first `up`
initializes an empty database.
Each audit loaded its whole result set into memory before iterating, which
exhausted a 14 GB container on a production-sized database (1.6M alleles,
7.3M mapping records). Rows now stream in batches of 5000, keeping memory
flat; results are unchanged.
…erver extras

The measurements view model imported its relationship enum from
lib.allele_measurements, which pulls in fastapi through permissions; the
enum now lives in models/enums. The audit-scope and URN-lock tests use the
Postgres session fixture, so they skip when psycopg2 is absent.
…he 2026.3.0 substrate

Describe measured and candidate projection groups, the ValidTime base class, the
30-minute stall window and the public dump's mapped HGVS columns.
…groups

Each mapping record's measured variant now has a projection group holding its
genomic, coding and protein HGVS, written from the mapping alone so intronic, UTR,
multi-codon and start/stop changes keep their coding and protein descriptions.

- Genomic assays without a run transcript fall back to an NM_/ENST target
  accession, then the MANE Select transcript at the locus.
- Genomic HGVS is normalized before projection; inversions are written as the
  equivalent delins, since VRS cannot represent them; identities keep their span.
- A failed projection never replaces a group an earlier run wrote.
- A changed standalone re-projection reports the reverse translation links it
  detaches as detached_rt_links.
- Reverse translation joins its candidates to the measured group or its own
  candidate groups; serving reads mapped HGVS from the measured group.
- VRS identifier search also matches pre-mapped identifiers and returns a
  deterministic order.
- project_measured_variants script fills groups for backfilled records.
…queue

With the main worker capped at two jobs, hour-long mapping jobs could hold
both slots and leave new score sets waiting for variant creation. start_pipeline
and create_variants_for_score_set now go to a separate ARQ queue consumed by a
second worker process (FastLaneWorkerSettings).

- arq_queue_name() routes each JobRun; every enqueue and cleanup's ARQ
  status/abort checks resolve the queue through it.
- The main worker still registers the fast-lane jobs so any already on the
  default queue at deploy time still run.
- Requires the fast-worker container from the deployment repo.
…omic projection

An insertion at an exon junction can be written on either side of it. Both
spellings identify to one coding allele, but only the in-exon one projects to
the genome, and an allele links to a record once. Link pairs with a genomic
projection first so the shared allele joins that pair's group rather than a
coding-only group.
…s missing

cdot's REST client returns None for any non-OK response and caches it for the
provider's lifetime, so a cdot-rest 5xx read as "no such transcript" (or no
transcripts in a region) until the worker restarted. The MANE Select lookup
then returned None, and the measured projection overwrote stored coding and
protein HGVS with a group lacking them while reverse translation reported
success.

cdot_rest() now builds a provider that treats only a 404 as missing, raises on
any other error status and sets a request timeout. A transcript a region lists
but cdot no longer has is skipped by the MANE lookup.
… the submitted HGVS

CAR lists its own normalized spellings of an allele, not the submitted one, so
an indel or identity written differently from CAR's form was rejected as a
malformed response even though the positional answer was right. On staging that
dropped CAIDs for about 0.4% of submissions, all codon-level delins and
insertions from reverse translation, and a forced re-registration skipped those
alleles instead of verifying them.

The count check already ties each response to its submission, so the CAID is
linked as usual. An unlisted spelling is recorded on the event as
submitted_hgvs_unlisted with the submitted HGVS, counted in the job outcome as
unlisted_hgvs_allele_count, and summarized in one warning per job.
…'s shared projection

A measured coding change across an exon junction (a codon split by an intron)
projected to a genomic change that also deleted the intron, because hgvs maps
the two ends of an interval separately. The measured projection and reverse
translation's candidates now use variant-annotation's coding_to_genomic and
genomic_to_coding, which give such a change no projection and write an inv as
the delins on its trimmed span.

- Pin variant-annotation to v1.2.0-beta.1, which brings variant-translation
  1.2.0.
- WorkerCoordinateTranslator gains c_to_n for the span check.
- The dump README and CHANGELOG note that a split-codon change has NA for
  post_mapped_hgvs_g.

Score sets that already ran reverse translation need it re-run to retire
intron-deleting candidates and refresh measured groups at split codons.
…stribution backfill

ScoreSet.modification_date has onupdate=date.today, so writing the summary
through the ORM stamped every score set as modified on the day the backfill
ran. The summary is now written with an UPDATE that sets modification_date to
itself.
…erpart

projectionOf names another spelling of the same nucleotide change. Since
projection groups landed, the protein consequence in a measured group was also
given the measured allele as its projectionOf, so the UI's allele grouping
folded the protein into the measured nucleotide group: on a protein-change page
with a nucleotide measurement the nucleotide row was marked as the page's own
variant, and rows reordered with the selected measurement.

The protein's relation (translation_of) already says what it is to the
measured allele, so it no longer carries projectionOf in variant or allele
detail.
@bencap bencap self-assigned this Oct 15, 2026
@bencap
bencap merged commit 2813a52 into main Oct 15, 2026
9 of 10 checks passed
@bencap
bencap deleted the release-2026.3.0 branch October 16, 2026 17:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment