Skip to content

Read each SGDP workspace tar's samples from its own callset.json (#724) - #728

Merged
NoopDog merged 6 commits into
mainfrom
noopdog/724-sgdp-tar-callset
Oct 22, 2026
Merged

NoopDog merged 6 commits into
mainfrom
noopdog/724-sgdp-tar-callset

Conversation

@NoopDog

@NoopDog NoopDog commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #724

What changed

  • Which tars are read. The tar fetcher now also reads a GenomicsDB workspace's callset.json for the folders header_classifier.CALLSET_FOLDERS declares: ANVIL_T2T_CHRY's SGDP_CHM13v2/genomics_db and SGDP_GRCh38/genomics_db. An SGDP tar and a 1000 Genomes tar can share a file name, so the pipeline binds each record's dataset and folder into the new FileTypeConfig.samples_member hook. The fetcher asks that hook with the member names, as it asks kept_member.
  • Fetch and cache. The member is read after the head walk, in the tar's first and last MiB (_read_kept). Its text is cached beside vcfheader.vcf as TarEvidence.callset, with a looked-for flag, callset_read.
    • A read that fails is not marked looked for, so the next run reads it again. The tar keeps its member names and classifications.
    • The hook is asked before the cached text is used. The cache is keyed by md5, so a file of the same bytes in a folder not declared gets no callset.
  • Parse. callset_samples moved from external_lineage into cohort_steps, so the classifier and the Workaround for #624: link ANVIL_T2T_CHRY's 1000 Genomes workspace tars to the Schatz lab's published sample maps, checked by each tar's callset.json #710 importer share one parser. parse_tar_head parses each distinct text once per run. It gives ParsedTarHead.samples, or why there are none: no_callset, callset_unreadable or not_a_callset.
  • Samples. TAR_CONFIG.samples reads through a new rule, code_rules.TAR_CALLSET_SAMPLES (tar_callset_samples). It takes all the names through rules/sample_namespaces.yaml, or none.
    • SampleReader gains two optional hooks, used only by the tar reader. applies keeps a tar whose callset was not wanted out of the counts. why_unread puts the cause on an unread reading.
    • Every producer that reads samples now writes details.unread_samples, the files per cause per dataset, beside sample_outcomes and unrecognised_samples.
  • Reconcile is unchanged. A tar's samples settle as any file's do.
  • Docs and tests. CLAUDE.md and the docstrings that list the sample readers are updated, and so is the contract's "What is not true yet" bullet on 2.11, which said the SGDP tars are not read. New tests cover the fetcher, the parse causes, the folder check, the producer's counts, and the cached callset withheld from a file it is not wanted for. The golden fixture gains the empty unread_samples key on the five producers that read samples.

Why

The 62,022 SGDP workspace tars in ANVIL_T2T_CHRY had no samples, so they reached no donor. The 1000 Genomes tars get theirs from the published sample maps (#710), but the SGDP maps aren't public (#624). Each tar names its own samples in callset.json.

Assumptions I made

  • The scope is a path check, declared in code. The issue asked for exactly that, so it is a dict in header_classifier, not YAML. Adding the 1000 Genomes tars is Check the 1000 Genomes workspace tars' samples against their own callset.json #725.
  • The raw text is cached, not the parsed names, as the issue asked, so a parser change needs no new read. That adds about 33 KB per SGDP tar to the tar cache, about 2 GB in all.
  • A compressed tar's callset.json is not read. The head walk keeps only one member, and a gzip stream can't be entered at its tail. The cause is counted, and the tar's classifications are not failed. No SGDP tar is compressed (measured 0).
  • The per-cause counts stay in the tar producer's own details. The reconcile report still shows a single unread total per dataset.
  • Deferred to Check the 1000 Genomes workspace tars' samples against their own callset.json #725, where reading the 1000 Genomes tars makes them matter (comment):
    • one download per window for both members;
    • a compressed tar's callset;
    • one record shape per member;
    • a general "members to keep" hook in place of the folder dict;
    • the external lineage importer reading the cached callset.
  • Left as they are after a no-context review, since none occurs in today's data:
    • A wanted tar whose whole fetch fails is counted only as "content unreadable", not by sample cause.
    • A compressed SGDP tar's callset is counted as callset_unreadable and asked again each run.
    • Two threads saving one md5 at once can drop a callset just read; the cost is a repeat read, and no tar md5 is shared.
    • The 2-byte gzip check assumes the server honours Range, as AWS and GCP do.
    • Duplicate sample names are dropped, as every sample reader does.
  • The run's counter misreports member-only reads. "From cache" counts a tar whose cache entry existed, even when only a member was read over the network. That predates this change; vcfheader.vcf (Link T2T's GenomicsDB tars to their cohort: sample maps, the tars' own step, values read through the map #621) behaves the same.

How to verify

The run used below is output/anvil/20261021_172513:

  • make classify at 60abcef ran the tar producer in about 1 h 54 min, and reconciled its own run.
  • 43 callset.json reads failed transiently in that pass. make classify-tar RUN_DIR=output/anvil/20261021_172513 re-read just those 43 (2 min, all read), then make reconcile RUN_DIR=… ran.
  • At the final code (4b302eb), make classify-tar RUN_DIR=… (from cache) and make reconcile RUN_DIR=… ran again. Every number below is unchanged. The run's tar file and reconciled/ are now from 4b302eb; its other 13 inference files are from 60abcef, and the later commits change no output for them. The two commits after 4b302eb change only docs and tests.

The issue's definition of done, item by item:

  • make classify, then make reconcile. Run as above. To repeat it on a fresh checkout of this branch: make classify (SGDP tars come from the cache if data/evidence/anvil/tar is shared, otherwise about 2 h of range reads), then make reconcile.

  • About 62,022 more files with samples. Count reconciled file records whose samples is set: 591,781 in run 20261019_225524, 653,803 here, +62,022.

  • About 62,022 more files reaching a donor (591,045 today). This holds only under a looser count than the one behind 591,045.

    Count 20261019_225524 this run
    files where every sample has a donor (the 591,045 measure) 591,045 591,045 (+0)
    files where at least one sample has a donor 591,333 653,355 (+62,022)
  • The tar producer's samples outcomes per dataset, with every tar not read counted by cause. In output/anvil/20261021_172513/tar_classifications.json, under metadata.details:

    • sample_outcomes: {"ANVIL_T2T_CHRY": {"named": 62022}}
    • unread_samples: per cause. The first pass had {"ANVIL_T2T_CHRY": {"callset_unreadable": 43}}; after the re-read it is empty, because every callset was read.
    • The run log prints the same as Header samples, … and Samples not read, … lines.
    • A tar whose callset is not wanted (1000 Genomes, ANVIL_T2T, any non-workspace tar) is not counted.
  • make test-all (3,264 + 98 passed), make lint-all (Pyright 0 errors), make format-check and make validate-metadata all pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VF7tbFa5ZTmYWZ6JWKiBBS

🐦 Built with cc-claude-tools

NoopDog and others added 2 commits October 21, 2026 17:25
The tar fetcher reads a GenomicsDB workspace's callset.json, after the head
walk, for the workspaces header_classifier.CALLSET_FOLDERS declares
(ANVIL_T2T_CHRY's SGDP_CHM13v2/ and SGDP_GRCh38/ genomics_db folders), and
caches it beside vcfheader.vcf with a looked-for flag (TarEvidence.callset).
The pipeline hands the fetcher each record's dataset and folder, since an
SGDP tar and a 1000 Genomes tar share names.

parse_tar_head parses it with callset_samples, moved from external_lineage
into cohort_steps so the producer and the #710 importer share one parser.
TAR_CONFIG's sample reader (code_rules.TAR_CALLSET_SAMPLES) takes all the
names through sample_identity or none. A tar whose callset was not wanted
is not counted; one wanted and not read is unread, its cause counted per
dataset under the producer's details.unread_samples.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF7tbFa5ZTmYWZ6JWKiBBS
…_samples, and simplify (#724)

Review round on the SGDP callset reader:

- The tar cache is keyed by md5 alone, so a cached callset.json was given to
  any file of the same bytes, whatever its folder. The fetcher now asks the
  samples member first and returns no callset for a file it is not wanted
  for, keeping the entry's text on disk.
- Every producer that reads samples writes details.unread_samples, empty or
  not, as it writes sample_outcomes and unrecognised_samples.
- The pipeline binds the file's dataset and folder into samples_member, so
  no fetcher takes them; one helper picks the cached or fresh callset on
  both paths; each distinct callset text is parsed once per run; docstrings
  and CLAUDE.md say only what the code covers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF7tbFa5ZTmYWZ6JWKiBBS
Co-authored-by: cc-claude-tools <cc-claude-tools@clevercanary.com>
Copilot AI balanced review requested due to automatic review settings October 22, 2026 03:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The cache-upgrade path incorrectly treats plain workspaces named .tar.gz as compressed, preventing sample extraction that succeeds on a fresh fetch.

1 open issue
What changed in this PR

Adds sample extraction from SGDP GenomicsDB workspace tars using each archive’s own callset.json, feeding the existing sample reconciliation process. Donor-link gaps tracked in #718 remain outside this change.

Changes:

  • Fetches and caches callsets only for declared SGDP folders, with retries for failed member reads.
  • Shares callset parsing with the external lineage importer and records unread-sample causes.
  • Adds regression coverage and updates fixtures and documentation.
File Description
tests/​test_fetchers.py Tests callset fetching, caching, retries, and folder isolation.
tests/​test_external_lineage.py Moves shared parser tests to cohort coverage.
tests/​test_cohort_steps.py Tests parsing, folder selection, and sample reporting.
tests/​fixtures/​golden/​expected_output.json Updates expected sample metadata.
src/​meta_disco/​sample_identity.py Adds tar sample reading and unread-cause hooks.
src/​meta_disco/​records.py Defines the unread-samples metadata key.
src/​meta_disco/​reconcile_samples.py Documents additional sample sources.
src/​meta_disco/​pipeline.py Binds folder context and aggregates unread causes.
src/​meta_disco/​header_classifier.py Declares eligible SGDP workspace folders.
src/​meta_disco/​file_types.py Enables tar callset and sample readers.
src/​meta_disco/​fetchers.py Fetches and caches selected callset members.
src/​meta_disco/​external_lineage.py Uses shared callset parsing and constants.
src/​meta_disco/​evidence.py Adds callset text and read-state fields.
src/​meta_disco/​cohort_steps.py Parses callsets and reports failure causes.
src/​meta_disco/​code_rules.py Registers the tar sample rule.
scripts/​generate_rules_report.py Updates sample-rule explanatory text.
CLAUDE.md Documents callset caching and sample extraction.

Review effort: Balanced

Comment thread src/meta_disco/fetchers.py Outdated
…s decide (#724)

The cached path declined a wanted callset.json on the file name's `.gz`
alone, while a fresh read checks the gzip magic: a plain workspace misnamed
`.tar.gz` with a header-only cache entry never had its callset read. Such an
entry is now walked again from its head, as for the kept member, and the
walk decides by the bytes. Copilot review, PR #728.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF7tbFa5ZTmYWZ6JWKiBBS
Co-authored-by: cc-claude-tools <cc-claude-tools@clevercanary.com>
Copilot AI balanced review requested due to automatic review settings October 22, 2026 04:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

A failed callset-only re-walk of a cached tar named compressed can suppress classifications and lineage supported by its cached evidence.

1 open issue
1 resolved since last review

Review effort: Balanced

Comment thread src/meta_disco/fetchers.py Outdated
…lk (#724)

The previous commit walked a cached tar named `.gz` again to learn whether
its bytes are compressed before reading its callset; a failed walk raised
FetchError and unclassified a tar its cached names and header classify. The
question is now one two-byte range read, asked only where the callset is
wanted, and a failure of it is the callset's unread cause, retried next
run. Copilot review, PR #728.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF7tbFa5ZTmYWZ6JWKiBBS
Co-authored-by: cc-claude-tools <cc-claude-tools@clevercanary.com>
Copilot AI balanced review requested due to automatic review settings October 22, 2026 04:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

No blocking issues remain, and the supplied regression coverage addresses scoped fetching, cache isolation, retry behavior, and sample-reader integration.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

NoopDog and others added 2 commits October 21, 2026 21:27
…ted paths (#724)

From a no-context review of PR #728: the fetcher docstring now says which
cached entries have only the callset read, that only a tar named `.gz` is
asked by its bytes, and that a file the callset is not wanted for gets none;
TarHead's fields are per file; CLAUDE.md says which tars are counted by cause.
New tests pin an uncached plain tar named `.gz` (and its bytes counted), an
HTTP failure of the gzip check, and a re-walked entry keeping its callset for
a file it is not wanted for; each fails under the mutation it guards.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF7tbFa5ZTmYWZ6JWKiBBS
Co-authored-by: cc-claude-tools <cc-claude-tools@clevercanary.com>
…allset.json (#724)

The "What is not true yet" bullet on 2.11 said the 62,022 SGDP tars are
"not read"; that is still so for the lineage importer, but their samples
are now read from each tar's own callset.json.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VF7tbFa5ZTmYWZ6JWKiBBS
Co-authored-by: cc-claude-tools <cc-claude-tools@clevercanary.com>
Copilot AI balanced review requested due to automatic review settings October 22, 2026 05:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The scoped fetch and cache changes fit the existing sample pipeline, with targeted regression coverage and no unresolved blocking findings.

No open issues 🎉

Review effort: Balanced

@NoopDog
NoopDog merged commit 5a740eb into main Oct 22, 2026
2 checks passed
@NoopDog
NoopDog deleted the noopdog/724-sgdp-tar-callset branch October 22, 2026 06:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Read each SGDP workspace tar's samples from its own callset.json

2 participants