Skip to content

Support disjoint assembling features for mid-region germline imputation (FR3 gap) - #91

Merged
PoslavskySV merged 6 commits into
mainfrom
feat/germline-imputation-fr3-gap
Jul 24, 2026
Merged

Support disjoint assembling features for mid-region germline imputation (FR3 gap)#91
PoslavskySV merged 6 commits into
mainfrom
feat/germline-imputation-fr3-gap

Conversation

@PoslavskySV

@PoslavskySV PoslavskySV commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

What

Adds support for a disjoint assembling feature to the MiXCR Amplicon Alignment block, so clonotypes can be assembled across an uncovered mid-region window.

  • WorkflowassemblingFeature may now be a disjoint, comma-separated list of pieces with explicit reference points, e.g. FR1Begin:FR3Begin(+40),FR3Begin(+46):FR4End. formatAssemblingFeature emits the MiXCR --assemble-clonotypes-by [{…},{…}] form; parseAssemblingFeature classifies fully-covered regions as exported-as-is and the partially-covered gap region, flanks, and full VDJRegion as germline-imputed.
  • UI — the "Assembling feature" dropdown gains a Custom (advanced) option that reveals a free-text field for entering an arbitrary MiXCR gene feature (including a disjoint one). UI-only; the model already types assemblingFeature as a free string.

Why

With 2×150 sequencing, long-CDR3 clones can have paired reads that don't overlap, leaving a short uncovered window in FR3. Default full-VDJRegion assembly drops those clones. A disjoint assembling feature that brackets the gap lets each mate fully cover one piece, so the clone survives; enabling "Impute non-covered parts from germline" reconstructs the skipped window (and the full VDJRegion) from the assigned V/J germline — the same way the parental/reference sequence fills the gap.

Testing

  • exportSpecs unit tests (grammar + region classification): 37/37, incl. 6 new disjoint cases.
  • Tengo compiles (pl-tengo check); UI type-check + lint + build green.
  • Added a wf.test e2e case (disjoint FR3-gap feature with imputation) exercising a disjoint feature end-to-end on the s1 fixture. Not yet run in a dev backend (native-module / registry-auth gap in the local env) — relying on CI to run it here.

Notes

  • The one behavior to confirm on a real MiXCR run: the exported column names for a disjoint feature (nSeq[{…}], isProductive[{…}]). The code extends the existing braced-range pattern (nSeq{CDR1Begin:CDR3End}, already covered by tests); if MiXCR names them differently, the wf.test surfaces it and the outputProductiveFeature/key wiring is the adjustment point.
  • Model untouched → no collision with the in-flight structurer migration.
  • @platforma-sdk/block-tools in the workspace catalog is 2.12.0 (latest 2.12.9) — pre-existing on main, not bumped here.

Greptile Summary

Adds configurable disjoint clonotype assembly across uncovered mid-region windows.

  • Workflow formatting now converts comma-separated feature pieces into MiXCR disjoint-feature syntax and classifies fully covered versus germline-imputed regions.
  • Export specifications now use the disjoint feature for clonotype keys and productivity while imputing the skipped regions and full VDJRegion.
  • The settings UI adds a Custom (advanced) assembling-feature input.
  • Unit and workflow tests cover formatting, region classification, column expectations, and an end-to-end FR3-gap configuration.
  • Important touched terms:
    • Assembling feature — the gene feature MiXCR uses to define clonotype sequences; changed from preset contiguous features to also support custom disjoint features.
    • Disjoint assembling feature — multiple separated feature pieces assembled as one clonotype key; newly formatted as MiXCR [{…},{…}] syntax.
    • Reference point — a named feature boundary with an optional nucleotide offset, such as FR3Begin(+40); newly parsed to determine whole-region coverage.
    • Germline imputation — reconstruction of uncovered sequence from assigned V/J germline genes; extended to the skipped gap, partially covered regions, flanks, and full VDJRegion.
    • Clonotype key — columns identifying a unique assembled clone; changed to use the exported disjoint-feature nucleotide sequence with best V and J genes.
    • VDJRegion — the full variable-diversity-joining region; classified as imputed whenever assembly uses a disjoint feature.

Confidence Score: 4/5

The empty custom assembling-feature path must be rejected or defaulted before merging because it currently causes the workflow to panic.

The new clearable custom field can persist an empty assemblingFeature, argument validation allows it through, and the export-spec parser then panics on the invalid shape.

ui/src/pages/SettingsPanel.vue, workflow/src/calculate-export-specs.lib.tengo

Important Files Changed

Filename Overview
ui/src/pages/SettingsPanel.vue Adds custom assembling-feature selection and input, but permits an empty value that reaches the workflow and fails.
workflow/src/calculate-export-specs.lib.tengo Formats and classifies disjoint features, constructs export columns, and selects the disjoint sequence as the clonotype key.
workflow/src/mixcr-analyze.tpl.tengo Converts comma-separated feature pieces into MiXCR disjoint-feature command syntax.
test/src/exportSpecs.test.ts Adds focused tests for disjoint formatting, coverage classification, clonotype keys, and productivity-column expectations.
test/src/wf.test.ts Adds an end-to-end workflow case using a disjoint FR3-gap feature with germline imputation.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  UI[Custom assembling feature] --> F[Format comma-separated pieces]
  F --> A[MiXCR assemble-clonotypes-by]
  F --> C[Classify covered regions]
  C --> N[Export covered regions as-is]
  C --> I[Impute gaps and VDJRegion]
  A --> K[Disjoint sequence clonotype key]
  N --> O[Clonotype outputs]
  I --> O
  K --> O
Loading

Fix All in Claude Code

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
ui/src/pages/SettingsPanel.vue:475-481
**Empty custom feature crashes workflow**

When a user selects Custom (advanced) and leaves or clears this field, the empty string passes argument validation and reaches `parseAssemblingFeature`, which panics because the value has no valid endpoints, causing the analysis run to fail.

Reviews (1): Last reviewed commit: "Add a "Custom (advanced)" assembling-fea..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

Comment on lines +475 to +481
<PlTextField
v-if="isCustomAssemblingFeature"
v-model="assemblingFeature"
label="Custom assembling feature"
placeholder="e.g. FR1Begin:FR3Begin(+40),FR3Begin(+46):FR4End"
clearable
>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Empty custom feature crashes workflow

When a user selects Custom (advanced) and leaves or clears this field, the empty string passes argument validation and reaches parseAssemblingFeature, which panics because the value has no valid endpoints, causing the analysis run to fail.

Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/pages/SettingsPanel.vue
Line: 475-481

Comment:
**Empty custom feature crashes workflow**

When a user selects Custom (advanced) and leaves or clears this field, the empty string passes argument validation and reaches `parseAssemblingFeature`, which panics because the value has no valid endpoints, causing the analysis run to fail.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code

The assembling feature can now be a disjoint, comma-separated list of pieces
(e.g. FR1Begin:FR3Begin(+40),FR3Begin(+46):FR4End) that brackets an uncovered
mid-region window. formatAssemblingFeature emits the MiXCR disjoint
--assemble-clonotypes-by [{..},{..}] form; parseAssemblingFeature classifies
fully-covered regions as exported-as-is and the partially-covered gap region,
flanks, and the full VDJRegion as germline-imputed. This recovers long-CDR3
clones whose 2x150 reads leave a ~6 nt uncovered window in FR3.

The "Assembling feature" dropdown gains a "Custom (advanced)" free-text option
for entering an arbitrary MiXCR gene feature (including a disjoint one).

Unit tests (exportSpecs) cover the grammar/classification; a wf.test e2e case
exercises a disjoint feature end-to-end. Based on the structurer migration (#90).
@PoslavskySV
PoslavskySV force-pushed the feat/germline-imputation-fr3-gap branch from ca117b8 to 06d4a75 Compare July 24, 2026 17:13
@PoslavskySV
PoslavskySV changed the base branch from main to chore/migrate-to-structurer July 24, 2026 17:13
Base automatically changed from chore/migrate-to-structurer to main July 24, 2026 17:40
exportClones -nFeature/-aaFeature/-isProductive parse a single composite
GeneFeature (pieces joined with "+"), not the "[{..},{..}]" list that
--assemble-clonotypes-by accepts — MiXCR rejected the list form at export with
"Unknown feature". The export-side formatAssemblingFeature now emits
"{A:B}+{C:D}"; the assemble side keeps the list form (verified working).
MiXCR's GeneFeature.encode names a range's start with a "Begin" point and its end
with an "End" point, canonicalizing boundary points (e.g. a range ending at
FR3Begin is exported as CDR2End, since FR3Begin == CDR2End). The block now
predicts the same canonical composite name, so the nSeq/aaSeq/isProductive columns
it references match MiXCR's actual exported headers — the downstream key-hash and
productive-filter were failing with ColumnNotFound on the non-canonical name.

Confirmed from the run: CDR1Begin:FR3Begin,CDR3Begin:FR4End exports as
{CDR1Begin:CDR2End}+{CDR3Begin:FR4End}.
MiXCR's GeneFeature.encode rewrites a composite feature's exported column header
unpredictably — ref-point duals (FR3Begin -> CDR2End) and whole-region ranges
collapsing to their name ({CDR3Begin:CDR3End} -> CDR3) — so predicting that name
is a losing game (each feature variant hit a new rule). For a disjoint assembling
feature the block now avoids the composite column entirely:

- clonotype key = the individual covered regions (nSeqCDR1, nSeqFR2, ... — names
  MiXCR produces verbatim) + bestVGene + bestJGene. Keying on all covered regions
  (not just CDR3) matters for synthetic fixed-framework libraries where CDR1/CDR2
  are independently diversified.
- productivity is driven off CDR3 (isProductiveCDR3), and the full-length imputed
  VDJRegion is the main sequence.
- no combined assembling-feature column is exported.

exportClones no longer receives any composite -nFeature/-isProductive arg, so the
downstream key-hash and productive-filter reference only real, verbatim columns.
Selecting "Custom (advanced)" and leaving the field empty let "" reach
parseAssemblingFeature, which panics ("must be in the format of 'begin:end'"),
failing the run (flagged in review). Fixes:
- argsValid now rejects an empty assemblingFeature, blocking the run until the
  field is filled;
- the Custom field shows a validation message when empty.

Also add the model package to the changeset, and correct a stale comment in
mixcr-analyze (the export side keys on covered regions, not a "+" composite).
@PoslavskySV
PoslavskySV merged commit d8882f4 into main Jul 24, 2026
11 checks passed
@PoslavskySV
PoslavskySV deleted the feat/germline-imputation-fr3-gap branch July 24, 2026 22:52
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