Count FORMAT value items in the oracle, not only INFO ones - #15
Merged
Merged
Conversation
A run over any VCF with a positional FORMAT key failed validation. The oracle reported the entire value-item layer as unexpected -- 401,606 extra hasValueItem/forAllele/FieldValueItem rows on a 100k-record GIAB file -- and 13_query_cost failed 3/3 replicates at its large scale. _emit_value_items is called from two places: the INFO pass, and append_expanded_sample_rdf for FORMAT. emitted_record_counters only ever walked parse_info_entries, so the FORMAT half was never expected. format_numbers was already a parameter of that function and was never read once -- the counting had simply never been written. The layer is expanded-only, because append_expanded_sample_rdf is the only emitter of it, so it is returned separately and merged by expected_census under representation == "expanded", exactly as the genotype layer already is. The emitter's two preconditions are mirrored rather than re-derived: at least one ALT, and split_value_items yielding items (a missing cell yields none). Why the fixtures never caught it: test-1k.vcf and test-10k.vcf declare no positional FORMAT key at all, while HG005 declares AD and ADALL as Number=R. The bug needed real data to appear. Adds EmittedTermCensusCoverageTests, which diffs every vocab term the emitter produces against everything the oracle counts, classifying each use by its position in the triple so vocabulary objects are not mistaken for census terms. This is the guard for the whole bug class -- it has now bitten twice, here and in the INFO items under raw INFO. It ships with a waiver list of 35 terms that are genuinely unmodelled today, grouped by feature: local alleles, phase sets, SV/confidence intervals, gVCF reference blocks, tandem repeats and Number=M base modifications. Each waived entry is a file shape that cannot pass validation, so the list is a to-do, not a licence. A second test fails if a waived term later becomes counted, so the list cannot rot. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
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.
The bug
Any VCF with a positional FORMAT key failed validation. The oracle reported the entire value-item layer as unexpected extra rows — 401,606 of them on a 100k-record GIAB file — and
13_query_costfailed 3/3 replicates at its large scale, on all three artifacts (aggregate, hdt, cottas).This is the mirror image of #14: there the oracle expected triples the graph did not have; here the graph has triples the oracle does not expect.
Root cause
_emit_value_itemsis called from two places:vcf_rdfizer.py:4252vcf_rdfizer.py:2842(append_expanded_sample_rdf)emitted_record_countersonly ever walkedparse_info_entries(info). The FORMAT half was never counted — andformat_numberswas already a parameter of that function, never read once. The counting had simply never been written.The fix
The layer is emitted only by
append_expanded_sample_rdf, so it is returned separately and merged byexpected_censusunderrepresentation == "expanded"— exactly as the genotype layer already is. The emitter's two preconditions are mirrored rather than re-derived: at least one ALT, andsplit_value_itemsyielding items (a missing.cell yields none).Why no fixture caught it
The fixtures declare no positional FORMAT key at all, so the bug needed real data to appear. Arithmetic checks out: 2 fields × 100k records × ~2 items (
R= ref + each ALT) ≈ 400k, against the observed 401,606.The guard
EmittedTermCensusCoverageTestsdiffs every vocab term the emitter produces against everything the oracle counts, classifying each use by its position in the triple so vocabulary objects (e.g.vcfc:ExpandedRepresentation, the object ofrepresentationProfile) are not mistaken for census terms. That distinction matters — without it the scan reports 39 false positives.This is the guard for the whole bug class, which has now bitten twice. It ships with a waiver list of 35 terms that are genuinely unmodelled today:
LocalAlleleSet,hasLocalAllele,localIndex, …PhaseSet,inPhaseSet,phaseSetId, …VariantEvent,svClaim,ciLower,endPosition, …ReferenceBlock,referenceBlockLength, …TandemRepeatAllele,repeatSequenceIndex, …Number=Mbase modificationsBaseModification,modifiedResidue, …Each waived entry is a file shape that cannot pass validation today, so the list is a to-do rather than a licence — phased data (
PS) and SV callsets are common, and a gVCF would hit the reference-block cluster. A second test fails if a waived term later becomes counted, so the list cannot rot.I verified the guard is not vacuous: removing the phase-set terms from the waiver makes it fail with exactly those terms named.
Verification
377 existing tests plus 8 new ones, all passing. Unit tests drive
emitted_record_countersdirectly rather than going throughvalidation_fixtures, deliberately: that fixture is a hand-written mirror of the emitter, so a test built on it can be wrong in the same way the oracle is — which is how this survived.End-to-end re-run of
13_query_coston the benchmark VM is in flight; I'll confirm 6/6 before this is merged.🤖 Generated with Claude Code