Repository navigation
Conversation
Read a sample's donors from its own row: IGVF's submitter sample.donors (collected_from) and AnVIL's anvil_biosample.donor_id (collected_from_repository_id, read as the donor records its ids tie to). A sample may be collected from several donors: CollectedFrom.donor becomes donors, each source names a set, and different sets give none. A line with a donor no rule recognises, or an id no donor ties to, names none of its donors and is counted. The report adds pooled, without_donor and unresolved_repository_links. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VF7tbFa5ZTmYWZ6JWKiBBS
…ds them (#770) From the local review round: - One deferred loop, _read_donor_links, resolves a sample's donors from collected_from and collected_from_repository_id lines; tied keeps the rule that recognised each record, replacing donor_rule. - models.REPOSITORY_ISSUED_FIELDS says which fields only a published table carries; EntityTable.kinds says what a table's identifier names, read by the loader and by check-entity-map. - check-entity-map splits list cells only in a sample table's donor column and skips PED's 0 only in a parent column, as reconcile does. - The loader refuses collected_from_repository_id without a published donor table to tie its ids to. - Reuse sample_identity.in_identifier_order (was _sorted) and the new source_evidence.cell_values (from_line, value_map.match_key); pooled is derived, and without_donor, a copy of no_donor's lengths, is gone. - Docstrings and the SampleRecord description name the donor sets. 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>
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
The breaking collected_from.donor-to-donors schema change requires human confirmation of browser-loader compatibility and rollout sequencing, especially since browser issue #221 was inaccessible.
No open issues 🎉
What changed in this PR
This PR extends entity reconciliation to link samples to their donors through submitter metadata and AnVIL biosample records, addressing #770 and #711 and supporting pooled samples.
Changes:
- Reads donor links from IGVF’s
sample.donorsand AnVIL’sanvil_biosample.donor_id, resolving repository identifiers across datasets. - Replaces
collected_from.donorwithcollected_from.donors; requires sources to agree on the complete donor set and reports pooled samples and unresolved links. - Updates schema, map validation, tests, and documentation. The browser loader needs the corresponding plural-field update.
| File | Description |
|---|---|
| tests/test_sgdp_entities.py | Updates SGDP assertions for donor lists and set conflicts. |
| tests/test_sample_donors.py | Adds pooled-donor, resolution, conflict, and validation tests. |
| tests/test_entities.py | Updates entity tests for the expanded map and output shape. |
| src/meta_disco/value_map.py | Reuses the shared cell-value helper. |
| src/meta_disco/sources/anvil_entity_map.yaml | Maps submitter and published sample-to-donor links. |
| src/meta_disco/source_evidence.py | Adds a shared scalar/list-cell helper. |
| src/meta_disco/schema/classification.yaml | Defines donor lists and the repository-link evidence field. |
| src/meta_disco/schema/classification_model.py | Regenerates models for the schema changes. |
| src/meta_disco/sample_identity.py | Exposes canonical identifier ordering for reuse. |
| src/meta_disco/reconcile.py | Updates the reconciliation docstring for multiple donors. |
| src/meta_disco/reconcile_samples.py | Reuses the shared cell-value helper. |
| src/meta_disco/reconcile_entities.py | Resolves donor sets and updates conflict and reach reporting. |
| src/meta_disco/models.py | Declares the new identity-link field and its kind. |
| src/meta_disco/entity_map.py | Validates sample-link mappings and published donor requirements. |
| src/meta_disco/entity_evidence.py | Documents the expanded identity-link evidence. |
| src/meta_disco/anvil_entities.py | Checks sample donor columns and table identifier kinds. |
| docs/claims-contract.md | Documents donor-set agreement semantics. |
| docs/adr/0002-derivation-graph.md | Records the pooled-sample design extension. |
| CLAUDE.md | Updates architecture guidance for sample-to-donor links. |
Review effort: Balanced
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #770
Closes #711
What changed
sample.donors: a list cell, declared in the entity map ascollected_fromon thesampletable.anvil_biosample.donor_id: a list of AnVIL donor UUIDs, written as a new entity evidence field,collected_from_repository_id.repository_ids(Record each AnVIL donor's and biosample's identifiers by namespace #669), after every file is read, so a UUID tied in another dataset still resolves.reconcile_entities._read_donor_links.SampleRecord.collected_fromis now{donors: [...], named_by}, replacing{donor, named_by}. The list has one donor for most samples and several for a pooled one, in identifier order.collected_from_conflicts[].named.unrecognised_linksand the newunresolved_repository_links.entities.samples.pooledcounts the samples linked to more than one donor.collected_from, and only that (EntityTable.kinds: a table's slots are one kind's).collected_from_repository_id, but only beside a published donor table.check-entity-mapreads a sample table's donor column as donors, list cells split, as reconcile reads it.CollectedFrom.donors(multivalued), and the field pattern takescollected_from_repository_id. The pydantic model is regenerated.source_evidence.cell_values(a list cell's elements, else the cell) andsample_identity.in_identifier_order, renamed from_sorted.collected_frompassage, an "Extended by IGVF: link each sample to its donor (the submitter's sample.donors, or AnVIL's anvil_biosample.donor_id) #770" note on ADR-0002 decision 3, CLAUDE.md and the entity map's comments.Why
None of AnVIL_IGVF_Mouse_R1's 6,786 files reached a donor at reconcile, though AnVIL links them. Measured on the anvil15 manifests, 2026-10-28:
donor_idname the same donors on all 1,935 IGVF samples.collected_fromcould link only about 40 files. That's why the field became a set.Assumptions I made
meta-disco-browser's loader readscollected_from.donor(clevercanary/meta-disco-browser#221). Until it readsdonors, a run reconciled at this commit won't load there.data/entity_evidenceuntil this merges. Other clones' schema would refuse the new field. I imported it into a scratch root for the measurement below.How to verify
The issue's checklist and #711's, each with its step:
donor_reachshows about 6,757 files with every sample having a donor, and its 183 mouse donors are each reached.make check-entity-map. It should report18 tables across 9 dataset(s)and no problems.make import-anvil-entities, thenmake reconcile.<run>/reconciled/reconcile_report.json, readentities.donor_reach.AnVIL_IGVF_Mouse_R1. Expect{"every_sample_has_a_donor": 6757, "files_with_samples": 6757}. Main had 6,717 "one sample without donor" and 40 "several samples, some without donor".entities.unreached.AnVIL_IGVF_Mouse_R1. It should have nodonorkey (main: 183 donors).entities.samples.collected_from_conflictsshould be[].collected_from_by_sourceshould showpublished_value: 5146, AnVIL's link agreeing with the rule.unresolved_repository_linksshould list ANVIL_HPRC's 2 UUIDs that tie to no donor record anywhere, NA20300's and HG01442's. Both samples keep their donor through the Coriell rule.collected_frompointing to that donor's record.entities.samples.collected_from.igvfshould be1935, andpooledshould be305.reconciled/samples.ndjson.gz,IGVFSM9197PTERshould havecollected_from.donors == [{"id": "IGVFDO5085MJSP", "namespace": "igvf"}], named by bothrepository_metadata(sample.donors) andpublished_value(anvil_biosample.donor_id).collected_from, by dataset.entities.samples.no_donorlists them per dataset. Only ANVIL_T2T_CHRY's 1 remains, as on main.make test, plustests/test_sample_donors.pyfor:Measured on a scratchpad copy of run
20261027_210224, reconciled at152aa32against a fresh entity import. The fullmake classifyrun at the PR head is due after review.🤖 Generated with Claude Code
https://claude.ai/code/session_01VF7tbFa5ZTmYWZ6JWKiBBS
🐦 Built with cc-claude-tools