Skip to content

fix(consensus): sort-order + error-rate guards and duplex rejection rows (CONS-01, DUP-01, R2-MET-05, R2-UCC-01) - #508

Merged
nh13 merged 2 commits into
mainfrom
nh/fix-consensus-sort-order-guards
Jul 12, 2026
Merged

nh13 merged 2 commits into
mainfrom
nh/fix-consensus-sort-order-guards

Conversation

@nh13

@nh13 nh13 commented Jul 9, 2026 •

Copy link
Copy Markdown
Member

Burn-down PR #10 of the fgbio↔fgumi behavioral-parity tracker. Four findings, all verified end-to-end against fgbio 4.1.0. Stacked on #505 (nh/fix-codec-fr-pair-rejection) because the rejection-machinery findings touch the same files (caller.rs, codec_caller.rs, consensus.rs, rejection.rs); retarget to main once #505 merges.

CONS-01 (S3) — consensus sort-order guard

fgbio's UmiConsensusCaller.checkSortOrder errors unless the input is template-coordinate sorted; simplex/duplex/codec had no such guard (only group/dedup did), so coordinate-sorted standalone input silently interleaved molecules and split every one of them — corrupting the entire output. Added check_consensus_sort_order (mirroring fgbio's three cases exactly: template-coordinate → accept, SO:unsorted+GO:query → warn, else → error), wired into the already-read header of both the threaded and single-threaded paths of all three callers (stdin-safe; no re-open).

DUP-01 (S3) — duplex error-rate validation

fgbio's CallDuplexConsensusReads requires errorRate{Pre,Post}Umi > 0 (Q0 ⇒ P(err)=1). duplex accepted a raw u8 with no check. Added Duplex::validate() rejecting a zero pre/post-UMI error rate. (codec already guarded this; simplex correctly allows >= 0.)

R2-UCC-01 (S4) — reject fragment reads

fgbio's DuplexConsensusCaller partitions unpaired/fragment reads out and rejects them as NonPairedReads; fgumi folded them into the strand groups where they were mis-accounted. Added a NonPairedReads rejection reason and partition/reject them in consensus_reads.

R2-MET-05 (S4) — seed the duplex KV rejection rows

fgbio seeds and always emits per-caller rejection rows (initializeRejectCounts). The duplex caller emits raw_reads_rejected_for_{non_paired_reads,single_strand_only,potential_umi_collision} even at zero; fgumi emitted only the four vanilla rows. Replaced the fixed core/optional split with per-caller seeding driven by a new ConsensusCallerKind, re-keyed the collision reason to fgbio's potential_umi_collision, and aligned the three newly seeded rows' descriptions. (Row-set half only; #504 owns the remaining description-text parity.)

Parity evidence (fgbio 4.1.0 ↔ fgumi, before → after)

  • CONS-01 — coordinate-sorted input: fgbio exit 1 (reject); fgumi BEFORE exit 0 (silently accepted); fgumi AFTER exit 1 (rejects); template-coordinate input AFTER exit 0.
  • DUP-01 — --error-rate-pre/post-umi 0: fgbio exit 1; fgumi BEFORE exit 0; fgumi AFTER exit 1; defaults exit 0.
  • R2-MET-05 — duplex KV rejection rows: fgbio 7; fgumi BEFORE 4; fgumi AFTER 7 (keys match fgbio exactly).
  • R2-UCC-01 — one fragment read: fgbio non_paired_reads=1; fgumi BEFORE no such row; fgumi AFTER non_paired_reads=1.

Reproducers: reports/fgbio-parity-fixtures/cons-01-dup-01-consensus-guards.sh and reports/fgbio-parity-fixtures/r2-met-05-ucc-01-duplex-rejection-rows.sh.

cargo ci-fmt, cargo ci-lint, and cargo ci-test (2221 tests) all green.

Summary by CodeRabbit

  • New Features

    • Added consensus input sort-order validation (template-coordinate accepted; query-grouped unsorted warns; others error with guidance).
    • Added template-coordinate header stamping plus a SAM builder option to apply it.
    • Added caller-aware consensus rejection metrics and KV “seeded” rows for vanilla/duplex/codec workflows.
  • Bug Fixes

    • Fragment/unpaired reads now map consistently to centralized “non-paired reads” metrics and are counted correctly.
    • Duplex rejects invalid zero pre-/post-UMI error-rate settings.
    • Fragment rejects no longer affect strand/MI accounting.
  • Tests

    • Updated and expanded unit/integration tests for sorting validation, fragment rejection/stats behavior, and caller-specific KV output.

@nh13
nh13 temporarily deployed to github-actions July 9, 2026 04:46 — with GitHub Actions Inactive
@coderabbitai

coderabbitai Bot commented Jul 9, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e2f346e6-43d8-47a6-a613-d916a7d4d780

📥 Commits

Reviewing files that changed from the base of the PR and between ae3dee2 and 4332216.

📒 Files selected for processing (8)
  • crates/fgumi-consensus/src/caller.rs
  • crates/fgumi-consensus/src/duplex_caller.rs
  • crates/fgumi-metrics/src/consensus.rs
  • crates/fgumi-metrics/src/lib.rs
  • crates/fgumi-metrics/src/rejection.rs
  • src/lib/commands/duplex.rs
  • src/lib/commands/simplex.rs
  • tests/integration/test_duplex_command.rs

Walkthrough

Consensus commands now validate template-coordinate input, duplex calling rejects fragment reads, and metrics expose caller-specific rejection rows. SAM builders and tests stamp required headers, while duplex validates UMI error-rate arguments.

Changes

Consensus pipeline updates

Layer / File(s) Summary
Template-coordinate input validation
crates/fgumi-sam/*, src/lib/commands/{common,codec,duplex,simplex}.rs
Adds SAM header helpers and validates consensus input ordering in threaded and single-threaded command paths.
Caller-specific rejection metrics
crates/fgumi-metrics/src/{consensus,rejection,lib}.rs
Adds NonPairedReads, caller-kind-aware KV metric seeding, updated rejection keys/descriptions, counters, exports, and tests.
Duplex fragment rejection
crates/fgumi-consensus/src/{caller,duplex_caller}.rs
Partitions paired and fragment reads, rejects fragments, optionally retains them, and maps the rejection to centralized non-paired metrics.
Command metrics and argument validation
src/lib/commands/{duplex,simplex}.rs, tests/integration/test_duplex_command.rs
Validates duplex UMI error rates, passes explicit caller kinds to metric rendering, and verifies seeded duplex rejection rows.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Command
  participant Header
  participant ConsensusPipeline
  Command->>Header: read BAM ordering metadata
  Command->>ConsensusPipeline: check_consensus_sort_order
  ConsensusPipeline-->>Command: accept input or return an error
Loading

Possibly related PRs

Suggested labels: fgumi duplex, fgumi simplex, fgumi codec

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main changes: consensus sort-order validation, duplex error-rate guards, and rejection-metric row updates.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch nh/fix-consensus-sort-order-guards

Comment @coderabbitai help to get the list of available commands.

@nh13
nh13 force-pushed the nh/fix-consensus-sort-order-guards branch from d881678 to 98be98d Compare July 9, 2026 04:56
@nh13
nh13 temporarily deployed to github-actions July 9, 2026 04:56 — with GitHub Actions Inactive
@codecov

codecov Bot commented Jul 9, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.37883% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.56%. Comparing base (4e5c27a) to head (4332216).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
crates/fgumi-consensus/src/duplex_caller.rs 96.66% 3 Missing ⚠️
src/lib/commands/duplex.rs 86.36% 3 Missing ⚠️
crates/fgumi-metrics/src/rejection.rs 88.23% 2 Missing ⚠️
crates/fgumi-sam/src/lib.rs 97.29% 2 Missing ⚠️
crates/fgumi-metrics/src/consensus.rs 98.38% 1 Missing ⚠️
src/lib/commands/codec.rs 97.05% 1 Missing ⚠️
src/lib/commands/simplex.rs 97.72% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #508      +/-   ##
==========================================
+ Coverage   92.52%   92.56%   +0.04%     
==========================================
  Files         165      165              
  Lines       99259    99634     +375     
==========================================
+ Hits        91839    92230     +391     
+ Misses       7420     7404      -16     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@nh13
nh13 force-pushed the nh/fix-consensus-sort-order-guards branch from 98be98d to a24cb2a Compare July 9, 2026 20:21
@nh13
nh13 temporarily deployed to github-actions July 9, 2026 20:21 — with GitHub Actions Inactive
@nh13
nh13 force-pushed the nh/fix-consensus-sort-order-guards branch from a24cb2a to e80c30b Compare July 9, 2026 20:39
@nh13
nh13 temporarily deployed to github-actions July 9, 2026 20:39 — with GitHub Actions Inactive
Base automatically changed from nh/fix-codec-fr-pair-rejection to main July 10, 2026 16:46
@nh13

nh13 commented Jul 10, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 10, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@nh13

nh13 commented Jul 10, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 10, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

…-01)

Two missing input guards on the standalone consensus callers, both matching
fgbio's UmiConsensusCaller / CallDuplexConsensusReads behavior.

CONS-01 (S3): fgbio's UmiConsensusCaller.checkSortOrder
(UmiConsensusCaller.scala:165-173) errors unless the input is
template-coordinate sorted; simplex/duplex/codec had no sort-order guard (only
group/dedup did), so coordinate-sorted standalone input silently interleaved
molecules and every molecule was split — corrupting the entire output.

Add check_consensus_sort_order (commands/common.rs) mirroring checkSortOrder's
three cases exactly (UmiConsensusCallerTest.scala:34-60): template-coordinate =>
accept; SO:unsorted + GO:query (no SS) => warn; anything else => error. It reads
only the already-read header and never re-opens the stream, so it is safe on
stdin. Wired into both the threaded and single-threaded header-read sites of
simplex/duplex/codec. New fgumi-sam helpers is_query_grouped_unsorted and
header_as_template_coordinate back the guard and the test-input builder.

DUP-01 (S3): fgbio's CallDuplexConsensusReads validates
errorRate{Pre,Post}Umi > 0 (CallDuplexConsensusReads.scala:131-132) — Q0 means
P(err)=1, a nonsensical prior. fgumi duplex accepted a raw u8 with no check. Add
Duplex::validate() rejecting a zero pre/post-UMI error rate. (codec already
guarded this; simplex correctly allows >=0 per fgbio, and its u8 is never
negative.)

Consensus command tests now stamp template-coordinate order on their input BAMs
(a new SamBuilder::set_template_coordinate_sort_order helper, plus the codec /
duplex input-writer helpers), matching fgbio's command tests which build input
with SamOrder.TemplateCoordinate.

Verified fgbio<->fgumi end to end (reports/fgbio-parity-fixtures/
cons-01-dup-01-consensus-guards.sh):
- CONS-01, coordinate input: fgbio exit 1 (reject); fgumi BEFORE exit 0
  (silently accepted); fgumi AFTER exit 1 (rejects, same message class);
  template-coordinate input AFTER exit 0.
- DUP-01, --error-rate-pre/post-umi 0: fgbio exit 1; fgumi BEFORE exit 0;
  fgumi AFTER exit 1; defaults AFTER exit 0.
@nh13
nh13 force-pushed the nh/fix-consensus-sort-order-guards branch from 37e2e83 to d52374b Compare July 11, 2026 01:11
@nh13
nh13 temporarily deployed to github-actions July 11, 2026 01:11 — with GitHub Actions Inactive
@nh13

nh13 commented Jul 11, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@nh13

nh13 commented Jul 11, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/fgumi-consensus/src/duplex_caller.rs`:
- Around line 709-713: The consensus tests cover only fragment-only MI groups;
add a mixed-family test containing paired reads and stray fragments. Using the
existing consensus test helpers and assertions near
test_consensus_reads_rejects_fragments_as_non_paired and
test_not_create_records_from_fragments, verify paired reads still generate a
duplex consensus while fragments are classified and counted as NonPairedReads.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 307fd7b0-5a02-4c90-92fd-e0641e707f90

📥 Commits

Reviewing files that changed from the base of the PR and between 4e5c27a and d52374b.

📒 Files selected for processing (12)
  • crates/fgumi-consensus/src/caller.rs
  • crates/fgumi-consensus/src/duplex_caller.rs
  • crates/fgumi-metrics/src/consensus.rs
  • crates/fgumi-metrics/src/lib.rs
  • crates/fgumi-metrics/src/rejection.rs
  • crates/fgumi-sam/src/builder.rs
  • crates/fgumi-sam/src/lib.rs
  • src/lib/commands/codec.rs
  • src/lib/commands/common.rs
  • src/lib/commands/duplex.rs
  • src/lib/commands/simplex.rs
  • tests/integration/test_duplex_command.rs

Comment thread crates/fgumi-consensus/src/duplex_caller.rs
@nh13
nh13 force-pushed the nh/fix-consensus-sort-order-guards branch from d52374b to 0fe810a Compare July 11, 2026 04:40
@nh13
nh13 temporarily deployed to github-actions July 11, 2026 04:41 — with GitHub Actions Inactive
@nh13
nh13 force-pushed the nh/fix-consensus-sort-order-guards branch from 0fe810a to ae3dee2 Compare July 11, 2026 04:57
@nh13
nh13 temporarily deployed to github-actions July 11, 2026 04:57 — with GitHub Actions Inactive
@nh13

nh13 commented Jul 11, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@nh13

nh13 commented Jul 11, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/fgumi-metrics/src/consensus.rs`:
- Around line 637-666: Update test_to_kv_metrics_duplex_seeds_duplex_rows to
assert the complete ordered key sequence returned by to_kv_metrics for both
Duplex and Vanilla, rather than checking keys only with any(). Include every
expected row in documented fgbio order, while preserving the existing
duplex-only inclusion and Vanilla exclusion checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c50c165e-3a05-4c44-8fd9-fa0a15f8164d

📥 Commits

Reviewing files that changed from the base of the PR and between d52374b and ae3dee2.

📒 Files selected for processing (8)
  • crates/fgumi-consensus/src/caller.rs
  • crates/fgumi-consensus/src/duplex_caller.rs
  • crates/fgumi-metrics/src/consensus.rs
  • crates/fgumi-metrics/src/lib.rs
  • crates/fgumi-metrics/src/rejection.rs
  • src/lib/commands/duplex.rs
  • src/lib/commands/simplex.rs
  • tests/integration/test_duplex_command.rs

Comment thread crates/fgumi-metrics/src/consensus.rs
…2-MET-05, R2-UCC-01)

Bring the duplex caller's KV consensus statistics in line with fgbio's per-caller
rejection-row model.

R2-UCC-01 (S4): fgbio's DuplexConsensusCaller partitions unpaired/fragment reads
out and rejects them as NonPairedReads
(`val (pairs, frags) = recs.partition(_.paired); rejectRecords(frags, NonPairedReads)`);
fgumi had no such reason and folded fragments into the strand groups, where they
were ignored by strand grouping but mis-accounted. Add a NonPairedReads rejection
reason (metrics + ConsensusMetrics field), map the caller-side FragmentRead reason
to it (it previously mis-mapped to SameStrandOnly), and partition + reject
fragments in DuplexConsensusCaller::consensus_reads.

R2-MET-05 (S4): fgbio seeds and always emits per-caller rejection rows via
`initializeRejectCounts` (`UmiConsensusCaller.scala:223-224`) — the duplex caller
emits `raw_reads_rejected_for_{non_paired_reads,single_strand_only,potential_umi_collision}`
even at zero, in addition to the four vanilla rows. fgumi emitted only the four
vanilla rows and put the duplex reasons in a non-zero-only "optional" set. Replace
the fixed core/optional split in ConsensusMetrics::to_kv_metrics with per-caller
seeding driven by a new ConsensusCallerKind {Vanilla, Duplex, Codec}: seeded rows
are always emitted (in fgbio order), and fgumi's finer-grained reasons are emitted
only when non-zero. Re-key the collision reason from `duplicate_umi` to fgbio's
`potential_umi_collision` and align the three newly seeded rows' descriptions with
fgbio.

(This is the row-set half of R2-MET-05; #504 owns the remaining description-text
parity. Codec does not currently emit KV stats, so codec seeding is defined but
unexercised; simplex already matched fgbio's four vanilla rows.)

Verified fgbio<->fgumi end to end
(reports/fgbio-parity-fixtures/r2-met-05-ucc-01-duplex-rejection-rows.sh):
- R2-MET-05: fgbio duplex emits 7 rejection rows; fgumi BEFORE emitted 4; fgumi
  AFTER emits the same 7 rows (keys match fgbio exactly).
- R2-UCC-01: with one fragment read, fgbio reports non_paired_reads=1; fgumi
  BEFORE emitted no such row; fgumi AFTER reports non_paired_reads=1.
@nh13
nh13 force-pushed the nh/fix-consensus-sort-order-guards branch from ae3dee2 to 4332216 Compare July 11, 2026 20:36
@nh13
nh13 temporarily deployed to github-actions July 11, 2026 20:36 — with GitHub Actions Inactive
@nh13

nh13 commented Jul 12, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 12, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@nh13

nh13 commented Jul 12, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 12, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@nh13

nh13 commented Jul 12, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 12, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@nh13
nh13 merged commit 97b85c9 into main Jul 12, 2026
10 checks passed
@nh13
nh13 deleted the nh/fix-consensus-sort-order-guards branch July 12, 2026 15:14
@nh13 nh13 mentioned this pull request Jul 12, 2026

This branch was previously deployed

1 inactive deployment
github-actions — 4332216a Deployed Jul 11, 2026 by nh13 via coverage #2297
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