Repository navigation
List AnVIL's donor and biosample ids on the records they tie to (#669) - #697
Merged
Merged
Conversation
Each DonorRecord and SampleRecord gains repository_ids: every AnVIL donor_id or biosample_id tied to it through the submitter row AnVIL made its record from (source_datarepo_row_ids, then that table's identifier column in the entity map). Many tie to one record; one tying to two records is refused at reconcile. - Entity map: `published` per kind (anvil_donor, anvil_biosample), each with its repository_id column; a table may name only its identifier, for the biosample source tables, and the check holds each table to every kind it serves. - Importer: each published record writes a donor_repository_id or sample_repository_id line beside its slots; datasets the map leaves out are listed with their record counts. - Reconcile: id lines are recognised by their field's kind and listed, never translated; a sample a biosample ties to gets a record even when no file names it; the report's entities block counts the ids per kind and dataset. - New identifier rule igvf_sample_accession (IGVFSM...), so IGVF's 1,935 biosamples tie. - ADR-0002 decision 2 corrected: AnVIL's UUIDs are stable across snapshots. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VF7tbFa5ZTmYWZ6JWKiBBS
- One kind-to-field map, in sample_identity beside EntityKind; entries() takes `fields` as iter_evidence does, so gather reads each entity file once. - The check holds each published table's source rows and repository id columns to a value on some row, so a misnamed column cannot import nothing. - Tables read for identifiers only are listed in the import's output, with their rows and any rows naming no identifier. - Reconcile refuses a repository id line in a file that is not a published table's; the two-record refusal names the earlier file again. - Left-out datasets are listed by the check too, skipping those with no published record; wording on AnVIL's ids matches what was measured. 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.
🟡 Changes recommended
Identifier-only imports can fail after publishing evidence, and malformed repository IDs can produce schema-invalid records.
2 open issues
What changed in this PR
Connects meta-disco’s donor and sample records to AnVIL’s stable identifiers, enabling explorer joins for #669.
Changes:
- Adds repository identifiers to entity records and reconciliation reports.
- Extends entity imports to biosamples and identifier-only tables.
- Adds IGVF sample recognition, tests, and documentation.
| File | Description |
|---|---|
| tests/test_sample_identity.py | Tests IGVF sample recognition. |
| tests/test_entities.py | Tests imports, identifier ties, and reporting. |
| src/meta_disco/sources/anvil_entity_map.yaml | Maps biosamples and identifier-only tables. |
| src/meta_disco/schema/classification.yaml | Defines repository identifiers. |
| src/meta_disco/schema/classification_model.py | Updates generated schema models. |
| src/meta_disco/sample_identity.py | Maps entity kinds to ID fields. |
| src/meta_disco/rules/sample_namespaces.yaml | Recognises IGVF sample accessions. |
| src/meta_disco/reconcile_entities.py | Attaches IDs and reports cardinality. |
| src/meta_disco/models.py | Defines repository-ID constants. |
| src/meta_disco/entity_map.py | Supports published tables by kind. |
| src/meta_disco/entity_evidence.py | Filters evidence by requested fields. |
| src/meta_disco/anvil_entities.py | Imports IDs and checks expanded mappings. |
| scripts/import_anvil_entities.py | Reports datasets omitted from mappings. |
| Makefile | Updates entity-import comments. |
| docs/adr/0002-derivation-graph.md | Corrects UUID stability documentation. |
| CLAUDE.md | Documents expanded entity handling. |
Review effort: Balanced
…sitory id (#669) - The entity map loader refuses a dataset none of whose tables reads a slot: its submitter generation would be empty, and an empty generation never supersedes an earlier one (Copilot). - Reconcile refuses a repository id line whose value is empty or more than one line, which would be no RepositoryIdentifier.id (Copilot). 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>
- Wording the anvil15 data contradicts: a person has an AnVIL donor in each dataset whose donors are made from a row naming them (MAGE and T2T_CHRY have none); the ids' stability is scoped to the one measured pair; MAGE's coriell_id is GM on 278 of 779 libraries; HPRC's mHomSap3. - The no-slot-table refusal states the reason the import actually has; the identifier clash message names all three columns; EvidenceEntry's docstring covers repository id entries. - Tests for what mutations showed untested: the kind in the tie key (one UUID as a donor's and a sample's), distinct id counting, the table a two-table record names, a left-out dataset not on disk, a carriage return in an id. 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>
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 #669. Slice 2 of epic #692.
What changed
DonorRecordandSampleRecordgainrepository_ids: every AnVILdonor_id/biosample_idthat ties to the record, each with itsrepository,datasetandtable. The schema class isRepositoryIdentifier. The field is absent where no ID ties.sources/anvil_entity_map.yaml,entity_map.py).publishednow names one table per kind:donor→anvil_donor,sample→anvil_biosample. Each gives itssource_rowsandrepository_idcolumn.identifierand read no donor slot. Six such tables are added, the ones AnVIL builds biosamples from: 1000Gsample, PRIMEDsample, HPRCsample, IGVFsample, MAGEsequencing_libraryand NIA_CARDsample.make check-entity-map,anvil_entities.check).source_rowsandrepository_idcolumns must hold a value on some row.make import-anvil-entities).donor_repository_idorsample_repository_idline carrying its ID, verbatim.anvil_biosamplegets its own file underanvil_published/.reconcile_entities).entity_evidence.entriestakesfieldsasiter_evidencedoes.SampleRecord.entitiesblock gainsrepository_ids: per kind, the records holding IDs, the distinct IDs, how many IDs each record holds, and per-dataset counts.samplesgainsnamed_by_files.igvf_sample_accession(IGVFSMplus 8 characters, names a sample only), so IGVF's 1,935 biosamples tie.Why
The explorer's donor and biosample pages are keyed by AnVIL's IDs, and our records are keyed by our identifier (
coriell:HG00183). Nothing joined the two. AnVIL's IDs were the same in prod anvil15 and dev's 1000G snapshot, and map many to one onto our records, so each record lists the ones that tie to it (#692's measurements).Assumptions I made
SampleRecordeven when no file names it, as donors already did (decided at the read-back).dataset_localfallback. ENCORE's HEK293 lines stay out of the map; minting them belongs with Capture the donors and biosamples AnVIL leaves out of T2T_CHRY, MAGE and nhp_dGTEx, and link them across datasets #559 (decided at the read-back).HG00183donor and biosample share775b7b7a….GM19908/NA19908). Identifiers stay uninterpreted at import (contract 1.1). It measured zero on anvil15 (decided at review).donor_repository_idvssample_repository_id) says which kind it names. It is not on the envelope; moving it there belongs with the envelope split (Entity evidence envelope: its own schema class instead of a runtime refusal of entity_identifier #694). Reconcile does not check that the field's kind matches the file's table, because it has no map to check against without hard-coding AnVIL's table names (decided at review).data/entity_evidence/and run20261014_004231are shared across clones, and main's reader refuses the new line fields. So the verification below ran in a scratch folder. After merge:make import-anvil-entities, thenmake reconcile RUN_DIR=output/anvil/20261014_004231.How to verify
Run from the repo root on this branch, with the anvil15 manifests on disk (
make download). The import and reconcile below write to a scratch folder and a copy of the run, never to the shareddata/oroutput/.uv run python scripts/import_anvil_entities.py --checkExpect
17 tables across 9 dataset(s), no problems, and two lines listing AnVIL_ENCORE_293T and AnVIL_ENCORE_RS293 as left out.uv run python scripts/import_anvil_entities.py --entity-root /tmp/ev669Expect the six
read for identifiers onlylines andWrote 57,373 entity lines.gzip -dc /tmp/run669/reconciled/donors.ndjson.gz | grep '"HG00183"'and the same forsamples.ndjson.gz. The report's block isjq .entities.repository_ids /tmp/run669/reconciled/reconcile_report.json.The issue's definition of done, mapped to these steps:
HG00183both appear in itsrepository_ids. Step 4: the donor listsb5f9eb7a…(1000G high coverage), and775b7b7a…listed for both PRIMED and T2T.CHM13/GRCh38(entities.unrecognised).mHomSap3,CHM13,GRCh38) are inentities.unrecognised.test_a_repository_identifier_tying_to_two_records_is_refused_not_chosen. On anvil15 none does: step 3 completes. A UUID that is both a donor's and a sample's (6,418 on anvil15) is not a conflict, because ties are per kind (test_one_uuid_as_a_donor_id_and_a_sample_id_ties_once_per_kind).ids_per_recordforsample: 5,148 records hold IDs, of which 1,946 hold 1, 2,471 hold 4, 707 hold 6 and 24 hold 8. One person's sample collects IDs from PRIMED, 1000G high coverage (two: itsparticipantandsamplerows), T2T, and MAGE (up to four:kgp_sampleand the sequencing libraries). On the donor side, 3,202 records hold 2 and 192 hold 1. The 8 donors with none are the same 8 Epic: donor and sample records, keyed by identifier, so files can be found by who they came from #692 found.Also checked: against the shared run's current reconciled output, every file record and sample list is identical.
donorsis identical apart fromrepository_ids, andsamplesdiffers only byrepository_idsand IGVF's 1,935 new sample records. Gate atdccb6ad:make test(3,011 passed, 9 skipped),make test-schema,make lint-all,make format-checkandmake validate-metadataall pass.🤖 Generated with Claude Code
https://claude.ai/code/session_01VF7tbFa5ZTmYWZ6JWKiBBS
🐦 Built with cc-claude-tools