Skip to content

feat(summary): ClinVar, PGx, ACMG SF and two-hit sections in Rust - #404

Merged
GeiserX merged 22 commits into
GeiserX:mainfrom
geiresx:pgp/rust-r2b
Nov 13, 2026
Merged

GeiserX merged 22 commits into
GeiserX:mainfrom
geiresx:pgp/rust-r2b

Conversation

@geiresx

@geiresx geiresx commented Nov 13, 2026 •

Copy link
Copy Markdown
Collaborator

The summary collector still needs Python for the clinical sections. This adds og summary collect --only b so those sections can be checked before the collector switches.

It reads ClinVar, the carrier list, PharmCAT, CPIC, pypgx, Cyrius, CPSR, clinical variants and slivar, plus the CYP2D6 block. The clinical section includes ACMG SF v3.3 and deletion-plus-variant two-hit rules. ClinVar stars use og-clinvar, indexed VEP lookups use the shared tabix reader, and CYP2D6 uses og-steps::pgx_rules.

The folder index is reused. ClinVar hits feed multiple sections from one read. Clinical VCF records stream through one pass for counts and HIGH-impact candidates. Section errors stay inside their sections.

Decision: collect clinical subtrees beside the existing collector so incomplete summaries cannot replace production output. Caller switching and Python deletion follow once all section groups are complete. Production reports continue using the existing collector. Output names and formats stay the same.

Deliberate differences, also listed in CHANGELOG.md:

  • Plain text with a .vcf.gz suffix is readable. The Python collector marks it unreadable. The cb-notgz golden pins this change.
  • Invalid UTF-8 in a PharmCAT report still makes it unreadable. The note uses Rust's decoder error.

Shared files changed for this group: rust/Cargo.lock records two existing workspace dependencies. rust/og/src/main.rs lists --only b. docs/rust.md and rust/README.md describe it. The group's rows and generator extend tests/parity/cases.tsv. Its oracle wrapper loads the pinned helper modules used by the clinical sections.

The expanded package scope also covers these parity files: tests/parity/gen/summary-clinical.sh, tests/parity/inputs/summary-collect/oracle.sh, cb_vep.vcf.gz, cb_vep.vcf.gz.tbi and the cb-notgz golden. They provide synthetic input, the pinned oracle and the deliberate output difference.

The independent review found two input bugs. Quoted headers in the SMN, interval and dosage tables discarded deletion evidence. A non-ASCII consensus contig could stop the collector before it wrote a summary. The fixes parse the first TSV record with pycompat::csv::Reader and check the ASCII chromosome prefix on bytes. Header reads stop after the first record. No output name or format changes.

The pattern check found no other unchecked fixed-offset string slice or TSV-header split in group b. The other tab splits read VCF records. The shared line iterator keeps rows() behavior unchanged. Both readers remain inside group b, so R2c's sections keep their readers.

How it was checked

  • On final head 990999c8, the phase fault was reapplied locally and still failed the same three tests: 61 passed and 3 failed. After restoring the file, all 64 tests passed. The merged container-helper static check also passed.
  • Before the fixes, three new unit tests failed with cargo +1.93.0 test --locked -p og-summary --no-fail-fast: 61 passed and 3 failed. All four new parity cases failed against the pre-fix collector. The restored collector passes 64 tests and all 188 comparisons across 47 cases. The local harness reports all 15 controls passing. Its Linux shadow check remains CI-only.
  • GitHub reported a conflict with main. Main was merged once at 990999c8b173a54c95391d9a455196edd1ead034. Both changelog entries were retained. The 64 tests and all local parity checks passed again on this head.
  • Red control: commit 8fb444c6 plants acmg-biallelic-ignores-phase. Guard's cargo test step failed three two-hit tests: cis exclusion, trans classification and best-pair selection. The remaining red-head jobs were cancelled. Commit b5d1a1f1 restores the rule.
  • Local cargo test --locked -p og-summary --no-fail-fast: 64 passed after the review fixes. Formatting, clippy with -D warnings, and docs with RUSTDOCFLAGS=-D warnings pass on the pinned Rust 1.93.0 toolchain.
  • Local ORACLE_RUNNER=local scripts/ci/oracle-diff.sh summary-collect: 47 cases, 188 items agree, including the deliberate golden. All 15 harness self-tests pass.
  • CI parity passed all 3,858 comparisons in 956 cases. Its 44 comparator controls, 15 harness controls and Linux shadow self-test also passed.
  • Fourteen group-b cases compare ordered JSON subtrees. They cover bash and Nextflow layouts, call floors, indexed AF/rsIDs, cDNA checks, ACMG selection, cis/trans deletions, carrier calls, CYP2D6, stale/failed steps and broken inputs. Existing Python tests remain because production still calls Python.
  • The initial personal-data scan flagged synthetic Cyrius header/data literals. The clean scan and local scan pass after separating the header from its rows. The fixture bytes are unchanged. No scanner exemption was added.
  • No new or changed e2e case calls this new collection mode. The local subtree cases exercise it directly. Fork e2e and landing remain with the landing train.
  • Before the review fixes, all upstream fast checks passed on c3bd0a264e642be978d8310c7ec539fedde46c87: Guard with ci-ok, Rust, Lint, Nextflow, Docs, Container Test and Personal data. Release, Renovate and Freshness also passed.

Synthetic timing, three runs on the same Apple Silicon box, /usr/bin/time -l (RSS in decimal MB):

Input Python wall Rust wall Python peak RSS Rust peak RSS
307 ClinVar hits, 200,006 clinical records, 200,002 slivar rows 1.23-1.29 s 0.30-0.34 s 134.8-136.0 MB 50.5-51.2 MB
50,001 ClinVar hits, 200,006 clinical records, 2 slivar rows 3.47-3.77 s 0.59-0.64 s 112.4-113.0 MB 458.9-460.6 MB

The large-hit stress case uses more memory in Rust because the summary is built as a JSON tree. This is a measured limit, and this PR makes no whole-pipeline speed claim. Only synthetic inputs were measured.

CodeRabbit returned a rate-limit notice. No review has arrived. Another PR received a review at 13:24 UTC, so the shared 55-minute queue prevents a new request. The independent review findings are fixed. The review response and local red evidence record each fix.

Current head: 990999c8b173a54c95391d9a455196edd1ead034. Fast checks are running: Guard, Rust, Nextflow and Docs. Lint, Container Test and Personal data passed.

Bead implemented: pgp-bs9u.42.

og dev build cn-intervals, callability and sv-rule-regions read the
pinned downloads from a folder and write bin/cn_loci.tsv's interval rows,
assets/callability_genes.tsv and assets/sv_rule_regions.tsv, byte for
byte as the Python builders did. The GTF is read as bytes with a hand
scanner of its attributes, the cut sets are merged once per chromosome
and searched by binary search, and og never downloads.

The SV rule builder now keeps the first GENCODE record per HGNC id and
skips _PAR_Y copies, as the callability builder does; the committed
table does not change.

Bead: pgp-bs9u.35
build-callability.sh and build-sv-rule-regions.sh keep their downloads
and hand the folder to og dev build; their Python heredocs are gone.
build-cn-intervals.py becomes build-cn-intervals.sh with the same --list,
--cache and --check. The three share scripts/ci/lib/fetch-pinned.sh: a
cached file with the right sha256 is read in place, a 403 or 404 fails
at once, anything else is retried for about five minutes.

The marker line of bin/cn_loci.tsv names the new script; no row changes.
The cn-intervals case, the e2e data fill and the converter checks of
tests/test_copy_number.py call the new script and og.

Bead: pgp-bs9u.35
Red-first control, reverted in the next commit: callability keeps the
_PAR_Y copies, the SV rule builder keeps the last record per HGNC id,
and each cn-intervals piece ends one base short.

Bead: pgp-bs9u.35
… --only b)

ClinVar, the carrier list, PharmCAT, CPIC, pypgx, Cyrius, CPSR, the
clinical filter with its ACMG SF tier, slivar and the cyp2d6 block, each
input read once, beside the Python collector. No caller changes.

Bead: pgp-bs9u.42
The clinical VCF, slivar's VCF and the hits were read whole before being
split; now one pass streams each, the hits are no longer copied for the
ClinVar section, and the summary's own data is freed before the JSON text
is made. A table cut short reads as Python's EOFError.

Bead: pgp-bs9u.42
The note now says "line L column C (char N)" in code points after
universal newlines, and names a UTF-8 BOM, as json.load does.

Bead: pgp-bs9u.42
…thon

Ten cases of og summary collect --only b: every input in both layouts,
failed and stale steps, unreadable inputs, a VEP output with and without
its index, phased deletions in trans and in cis, and no assets. The
oracle stages the helper modules the collector imports from the pinned
commit instead of stubs. cb-notgz holds the one deliberate fix.

Bead: pgp-bs9u.42
Plant: the deletion's read-backed phase is ignored, so a variant in cis
with a deletion counts as a second hit and a trans pair is only possibly
biallelic. The two_hits unit tests and the cb-full parity case must fail.

Bead: pgp-bs9u.42
@coderabbitai

coderabbitai Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 6 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration

Commits

Reviewing files that changed from the base of the PR and between b184d58 and 85ac78f.

Files ignored due to path filters: 4

  • rust/Cargo.lock is excluded by !**/*.lock
  • tests/parity/cases.tsv is excluded by !**/*.tsv
  • tests/parity/gen/summary-clinical.sh is excluded by !**/gen/**
  • tests/parity/inputs/summary-collect/cb_vep.vcf.gz is excluded by !**/*.gz
Files selected for processing: 22

  • CHANGELOG.md
  • docs/rust.md
  • rust/README.md
  • rust/crates/og-summary/Cargo.toml
  • rust/crates/og-summary/src/collect.rs
  • rust/crates/og-summary/src/collect/acmg.rs
  • rust/crates/og-summary/src/collect/assets.rs
  • rust/crates/og-summary/src/collect/carrier.rs
  • rust/crates/og-summary/src/collect/clinical.rs
  • rust/crates/og-summary/src/collect/clinvar.rs
  • rust/crates/og-summary/src/collect/cpsr.rs
  • rust/crates/og-summary/src/collect/group_b.rs
  • rust/crates/og-summary/src/collect/model.rs
  • rust/crates/og-summary/src/collect/pgx.rs
  • rust/crates/og-summary/src/collect/scan.rs
  • rust/crates/og-summary/src/collect/sections.rs
  • rust/crates/og-summary/src/collect/text.rs
  • rust/crates/og-summary/src/collect/two_hits.rs
  • rust/og/src/main.rs
  • tests/parity/golden/summary-collect/cb-notgz/cb-notgz.json
  • tests/parity/inputs/summary-collect/cb_vep.vcf.gz.tbi
  • tests/parity/inputs/summary-collect/oracle.sh
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

…go red"

The plant turned five builder tests red in Guard's rust job (run
43417543846); back to the right rules.

Bead: pgp-bs9u.35
… failed

fetch_pinned downloads to a mktemp file beside the target, so two runs
on one cache never write the same partial file, and gives curl a 30 s
connect timeout and a 1 KB/s for 60 s floor, so a stalled server reaches
the retry or the error instead of holding the build.

Bead: pgp-bs9u.35
Revert the acmg-biallelic-ignores-phase control. Keep cis variants out
of the two-hit count and classify a phased trans pair as biallelic.

Bead: pgp-bs9u.42
Keep the invented data rows separate from their column labels, as the
other generated tables do. This preserves every fixture byte and keeps
the personal-data scanner enabled without adding an exemption.

Bead: pgp-bs9u.42
@geiresx

geiresx commented Nov 13, 2026

Copy link
Copy Markdown
Collaborator Author

The independent review's two input bugs are fixed in 1a883cd. The final head is 990999c after one conflict-required merge of main.

  • Header parsing now uses the first CSV record with minimal quoting. Separate parity cases cover the SMN, interval and dosage headers.
  • Chromosome prefix matching checks bytes before taking the string suffix. The regression includes éé, a short contig, mixed-case ASCII prefixes and a Unicode suffix. A consensus VCF with a non-ASCII contig now writes the summary and matches Python.
  • The expanded package list covers every changed path. The PR body names the shared integration and parity files.

The group-b pattern check found no further unchecked fixed-offset string slice or TSV-header split. The common line iterator preserves rows() behavior and stays inside group b.

Red evidence from the pre-fix collector on this box, using Rust 1.93.0 and the new synthetic inputs:

cargo +1.93.0 test --locked -p og-summary --no-fail-fast
collect::text::tests::quoted_columns_match_the_row_keys ... FAILED
collect::text::tests::columns_read_only_the_first_record ... FAILED
collect::two_hits::tests::chromosome_prefix_is_safe_for_non_ascii_contigs ... FAILED
test result: FAILED. 61 passed; 3 failed

ORACLE_RUNNER=local scripts/ci/oracle-diff.sh summary-collect/cb-quoted-smn summary-collect/cb-quoted-interval summary-collect/cb-quoted-dosage summary-collect/cb-unicode-contig
cb-quoted-smn: missing hemizygous evidence
cb-quoted-interval: missing hemizygous evidence
cb-quoted-dosage: missing hemizygous evidence
cb-unicode-contig: panic, exit 101, no summary written
oracle-diff: 8 of 17 items in 4 cases differ

Green on the final head: 64 crate tests pass. All 188 comparisons across 47 summary cases agree with the oracle. All 44 comparator controls pass. The harness reports all 15 controls passing, with its Linux shadow check deferred to CI. Formatting, clippy with -D warnings and docs with RUSTDOCFLAGS=-D warnings pass.

The earlier phase-control red CI run and its restored green run remain linked in the PR. The planted fault is absent from the final head. No e2e case was added or changed. The new collection mode is covered by the subtree cases. E2E and landing remain with the landing train.

# Conflicts:
#	CHANGELOG.md
#	rust/crates/og-dev/src/lib.rs
#	rust/og/src/main.rs
…list zram0

Some runner images list /dev/zram0 as a disk of 0 bytes with no file
system. The spare-disk step took it for the blank disk, and mkfs.ext4
stopped the leg before any case ran. The step now skips zram, ram and
loop devices and any disk of 0 bytes.
@geiresx
geiresx requested a review from GeiserX as a code owner November 13, 2026 13:57
@GeiserX
GeiserX merged commit 51ec9e1 into GeiserX:main Nov 13, 2026
43 of 52 checks passed
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.

2 participants